title: Symfony2 RESTfull API Wsse authentication with Bcrypt
body: <p>I'm working on an RESTful API on where users are allowed to provide their user information in a HTTP header named x-wsse.</p>
  
  <p>Example: UsernameToken Username="JohnDoe", PasswordDigest="PasswordDigest", Nonce="Nonce", Created="CreatedTimestamp"</p>
  
  <p>PasswordDigest = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));</p>
  
  <p>$secret is the hashed, salted password.</p>
  
  <p>This header gets "caught" by a listener which "parses" it and validates it.</p>
  
  <p>The code snippet:</p>
  
  <pre><code>    $request = $event-&gt;getRequest();
  
      $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
      if (!$request-&gt;headers-&gt;has('x-wsse') || !preg_match($wsseRegex, $request-&gt;headers-&gt;get('x-wsse'), $matches)) {
          return;
      }
  
      $token = new WsseUserToken();
      $token-&gt;setUser($matches[1]);
  
      $token-&gt;digest   = $matches[2];
      $token-&gt;nonce    = $matches[3];
      $token-&gt;created  = $matches[4];
  
      try {
          $authToken = $this-&gt;authenticationManager-&gt;authenticate($token);
          $this-&gt;tokenStorage-&gt;setToken($authToken);
  
          return;
      } catch (AuthenticationException $failed) {
          // Authentication failed, some stuff will happen here
      }
  </code></pre>
  
  <p>The authentication manager has a authenticate method:</p>
  
  <pre><code>public function authenticate(TokenInterface $token)
  {
      $user = $this-&gt;userProvider-&gt;loadUserByUsername($token-&gt;getUsername());
  
      if ($user &amp;&amp; $this-&gt;validateDigest($token-&gt;digest, $token-&gt;nonce, $token-&gt;created, $user-&gt;getPassword())) {
          $authenticatedToken = new WsseUserToken($user-&gt;getRoles());
          $authenticatedToken-&gt;setUser($user);
  
          return $authenticatedToken;
      }
  
      throw new AuthenticationException('The WSSE authentication failed.');
  }
  </code></pre>
  
  <p>The validateDigest method does this:</p>
  
  <pre><code>protected function validateDigest($digest, $nonce, $created, $secret)
  {
      // Check created time is not in the future
      if (strtotime($created) &gt; time()) {
          return false;
      }
  
      // Expire timestamp after 5 minutes
      if (time() - strtotime($created) &gt; 300) {
          return false;
      }
  
      // Validate that the nonce is *not* used in the last 5 minutes
      // if it has, this could be a replay attack
      if (file_exists($this-&gt;cacheDir.'/'.$nonce) &amp;&amp; file_get_contents($this-&gt;cacheDir.'/'.$nonce) + 300 &gt; time()) {
          throw new NonceExpiredException('Previously used nonce detected');
      }
      // If cache directory does not exist we create it
      if (!is_dir($this-&gt;cacheDir)) {
          mkdir($this-&gt;cacheDir, 0777, true);
      }
      file_put_contents($this-&gt;cacheDir.'/'.$nonce, time());
  
      // Validate Secret
      $expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
  
      if ($digest === $expected) {
          $valid = true;
      } else {
          $valid = false;
      }
  
      return $valid;
  }
  </code></pre>
  
  <p>All this is covered in "The Cookbook" of Symfony2. Code and info: <a href="http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html" rel="nofollow">http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html</a></p>
  
  <p>As advised, all the user passwords are hashed with Bcrypt and - as advised (see <a href="http://php.net/password_hash" rel="nofollow">http://php.net/password_hash</a>) - I let PHP generate a hash for each password.</p>
  
  <p>I'm testing with Chrome REST console and my WSSE header is generated by: <a href="http://www.teria.com/~koseki/tools/wssegen/" rel="nofollow">http://www.teria.com/~koseki/tools/wssegen/</a>.</p>
  
  <p>Everything goes well, until I I"m validating the PasswordDigest. I'm getting a mismatch problem.
  This is caused by an "invalid salt revision".</p>
  
  <p>Since PHP generates my salt, and I can not find it stored anywhere (user table only has columns id, username, password, email, is_active), I can not "salt" the password myself at the "right" moment.</p>
  
  <p>How should I deal with this problem?</p>
  
  <p>I've been thinking of:</p>
  
  <ol>
  <li><p>Sending the plaintext username and password through a HTTPS request to a Symfony Controller which then returns the hashed password. This hashed password can be stored then to be reused in the next requests with "fresh" x-wsse header.</p></li>
  <li><p>Sending the plaintext username and password through a HTTPS request to a Symfony Controller which then returns a valid x-wsse header which can be used for only one request, because the nonce is only allowed to be used once.</p></li>
  </ol>
  
  <p>I'm not very happy with the solutions above and would like to know what you guys think... PHP is generating a salt itself, but where does it store that? Can I create a JS function which does the same / complies with <a href="http://php.net/password_hash" rel="nofollow">http://php.net/password_hash</a>?
  The explanation of using wsse with Symfony 2 on <a href="http://obtao.com/blog/2013/06/configure-wsse-on-symfony-with-fosrestbundle/" rel="nofollow">http://obtao.com/blog/2013/06/configure-wsse-on-symfony-with-fosrestbundle/</a> says salts may be public as long as they are unique for each user.</p>