Signed tokens vs. session-based tokens
Page 1 of 2 12 LastLast
Results 1 to 15 of 40

Thread: Signed tokens vs. session-based tokens

Hybrid View

  1. #1
    Senior Member
    Join Date
    Mar 2009
    Posts
    1,153

    Signed tokens vs. session-based tokens

    I haven't been here in a while; I miss this place. Hope everyone is doing well. Without further adieu...

    Up until recently I have always used the session-based method to implement security tokens. Page loads, a token is generated and printed to something like a hidden input field. The token is also stored in the session. When the form is submitted, the token goes along with it and the passed token is compared to what's in the session. If it matches, great, if not, some kind of error.

    However, this has issues if you want to have multiple tabs open with the application or do asynchronous calls. As soon as another tab is opened, the token stored in the session is going to be overwritten, invalidating the first tab. Likewise, with asynchronous calls, you basically have to wait in queue which doesn't make them asynchronous at all.

    The application I have been working on for my client was really hoping to be able to have multiple tabs open for their clients. Plus it is just convenient. So I did some searching online on how people handle this. One of the things I came across was signed tokens. Basically you remove the session part altogether and simply just verify the signed token when the form is sent. The entire value is a combination of a hashed value (with a server-side secret) and the token.

    Below is a class I created to handle this (some code has been lifted from Stackoverflow):

    PHP Code:
    /**
     * The Token class.
     */
    class Token
    {
        
    /**
         * The hashing algorithm used to hash the token data.
         *
         * @access public
         * @var string
         */
        
    const ALGORITHM 'sha256';

        
    /**
         * Generates a cryptographically signed token.
         *
         * @return string Returns the new token.
         */
        
    public static function generate() : string
        
    {
            
    $token  bin2hex(random_bytes(32));
            
    $hash   hash_hmac(self::ALGORITHM$tokenTOKEN_SECRET);
            
    $signed $token.'-'.$hash;

            return 
    $signed;
        }

        
    /**
         * Verifies a signed token.
         *
         * @param string $signedToken The signed token to verify.
         *
         * @throws InvalidTokenException
         * @throws TokenMismatchException
         *
         * @return void
         */
        
    public static function verify(string $signedToken)
        {
            
    $parts explode('-'$signedToken);

            if (
    count($parts) !== 2) {
                throw new 
    InvalidTokenException('Invalid token form!');
            }

            list(
    $token$hash) = $parts;

            if (
    $hash !== hash_hmac(self::ALGORITHM$tokenTOKEN_SECRET)) {
                throw new 
    TokenMismatchException('Token could not be verified!');
            }
        }

    The TOKEN_SECRET can be whatever you want, obviously stored outside the web root in a secure place. You could add some additional security by adding a timestamp to the token to allow expirations, or the user's ID so the tokens generated during their use of the application would be exclusive to them.

    I much prefer this method now as it's much simpler to implement and maintain than what I was doing previously. Also just feels much more elegant.

    Any feedback would be greatly appreciated. Would also be interested in hearing your methods of implementing security tokens.

    Thanks!
    Declare variables, not war.

  2. #2
    Settled 4 red convertible dalecosp's Avatar
    Join Date
    Jul 2002
    Location
    Accelerating Windows at 9.81 m/s....
    Posts
    8,378
    Well, it looks very nice ... seriously. Perhaps it needs this though:

    PHP Code:
    function __construct() {

       if (
    PHP_MAJOR_VERSION 7) {
          throw new 
    VersionMismatchException('PHP Version Insufficient!');
       }


    As for my way of doing it, I probably don't do it enough. Some of our systems go way back to 5.2 days (eek!) and so does the code, and tokens are generally used mostly for AJAX stuff and is based on hashing constants and secrets, server names and timestamps and poems and such-like ...
    /!!\ mysql_ is deprecated --- don't use it! Tell your hosting company you will switch if they don't upgrade! /!!!\ ereg() is deprecated --- don't use it!

    dalecosp "God doesn't play dice." --- Einstein "Perl is hardly a paragon of beautiful syntax." --- Weedpacket

    Getting Help at All --- Collected Solutions to Common Problems --- Debugging 101 --- Unanswered Posts --- OMBE: Office Machines, Business Equipment

  3. #3
    Senior Member Derokorian's Avatar
    Join Date
    Apr 2011
    Location
    Denver
    Posts
    2,242
    Quote Originally Posted by dalecosp View Post
    Well, it looks very nice ... seriously. Perhaps it needs this though:

    PHP Code:
    function __construct() {

       if (
    PHP_MAJOR_VERSION 7) {
          throw new 
    VersionMismatchException('PHP Version Insufficient!');
       }


    As for my way of doing it, I probably don't do it enough. Some of our systems go way back to 5.2 days (eek!) and so does the code, and tokens are generally used mostly for AJAX stuff and is based on hashing constants and secrets, server names and timestamps and poems and such-like ...
    Dale that is unnecessary, as this file will not compile in pre-php7 to be able to run the version compare anyway.
    Sadly, nobody codes for anyone on this forum. People taste your dishes and tell you what is missing, but they don't cook for you. ~anoopmail
    I'd rather be a comma, then a full stop.
    User Authentication in PHP with MySQLi - Don't forget to mark threads resolved - MySQL(i) warning

  4. #4
    Settled 4 red convertible dalecosp's Avatar
    Join Date
    Jul 2002
    Location
    Accelerating Windows at 9.81 m/s....
    Posts
    8,378
    Quote Originally Posted by Derokorian View Post
    Dale that is unnecessary, as this file will not compile in pre-php7 to be able to run the version compare anyway.
    Well, it was kind of a side comment that it wasn't mentioned. I'd wasted some time trying to run random_bytes() in the CLI and couldn't figure out why it didn't work until I went to the manual. Placing it in a script would've probably given me an error log entry, or at least I could've set error_reporting() up and gotten some feedback instead of scratching my head for a few minutes.
    /!!\ mysql_ is deprecated --- don't use it! Tell your hosting company you will switch if they don't upgrade! /!!!\ ereg() is deprecated --- don't use it!

    dalecosp "God doesn't play dice." --- Einstein "Perl is hardly a paragon of beautiful syntax." --- Weedpacket

    Getting Help at All --- Collected Solutions to Common Problems --- Debugging 101 --- Unanswered Posts --- OMBE: Office Machines, Business Equipment

  5. #5
    Senior Member Derokorian's Avatar
    Join Date
    Apr 2011
    Location
    Denver
    Posts
    2,242
    Quote Originally Posted by dalecosp View Post
    Well, it was kind of a side comment that it wasn't mentioned. I'd wasted some time trying to run random_bytes() in the CLI and couldn't figure out why it didn't work until I went to the manual. Placing it in a script would've probably given me an error log entry, or at least I could've set error_reporting() up and gotten some feedback instead of scratching my head for a few minutes.
    Ah cuz you just tried to use some function in it, if you tried to run the whole class, the parser would have died on the return type and scalar type hint
    Sadly, nobody codes for anyone on this forum. People taste your dishes and tell you what is missing, but they don't cook for you. ~anoopmail
    I'd rather be a comma, then a full stop.
    User Authentication in PHP with MySQLi - Don't forget to mark threads resolved - MySQL(i) warning

  6. #6
    Senior Member Derokorian's Avatar
    Join Date
    Apr 2011
    Location
    Denver
    Posts
    2,242
    Quote Originally Posted by Bonesnap View Post
    I haven't been here in a while; I miss this place. Hope everyone is doing well. Without further adieu...

    Up until recently I have always used the session-based method to implement security tokens. Page loads, a token is generated and printed to something like a hidden input field. The token is also stored in the session. When the form is submitted, the token goes along with it and the passed token is compared to what's in the session. If it matches, great, if not, some kind of error.

    However, this has issues if you want to have multiple tabs open with the application or do asynchronous calls. As soon as another tab is opened, the token stored in the session is going to be overwritten, invalidating the first tab. Likewise, with asynchronous calls, you basically have to wait in queue which doesn't make them asynchronous at all.
    I haven't gotten to the meat of your post yet, still working, but for this problem I use an array in session. Like this:
    PHP Code:
    class Token implement Serializable {
        use 
    magicGetserialize;

        protected 
    $form;
        protected 
    $token;
        protected 
    $expiry;

        public function 
    __construct(string $form) {
            
    $this->form $form;
            
    $this->token bin2hex(random_bytes(16));
            
    $this->expiry = new DateTime()->sub(new DateInterval('PT1H'));
        }

        public function 
    getToken() {
            return 
    $this->token;
        }

        public function 
    validate($form) {
            return 
    $this->form == $form && (new DateTime()) <= $this->expiry;
        }
    }

    // Adding a token
    $token = new Token('update_user');
    $_SESSION['tokens'][$token->getToken()] = $token;

    // Checking a token
    if (!isset($_SESSION['tokens'][$_POST['token']]) || $_SESSION['tokens'][$_POST['token']]->validate('update_user')) {
        
    handleError('invalid token supplied');

    Actually adding a token and checking a token are handled through a collection class that's pretty indepth but nicely encapsulates all the logic. This way I can add multiple tokens in a single page load as well (IE: login form, register form and forgot password form, which all load in a single request, though register and forgot are hidden until needed).

    I will look at the rest of your post this evening and give some feedback, but I'm sure its bomb :P
    Sadly, nobody codes for anyone on this forum. People taste your dishes and tell you what is missing, but they don't cook for you. ~anoopmail
    I'd rather be a comma, then a full stop.
    User Authentication in PHP with MySQLi - Don't forget to mark threads resolved - MySQL(i) warning

  7. #7
    High Energy Magic Dept. NogDog's Avatar
    Join Date
    Aug 2006
    Location
    Ankh-Morpork
    Posts
    14,692
    (I won't tell you about the kludge I wrote today to deal with a PHP 5.3 issue. )
    "Well done....Consciousness to sarcasm in five seconds!" ~ Terry Pratchett, Night Watch

    How to Ask Questions the Smart Way (not affiliated with this site, but well worth reading)

    My Blog
    cwrBlog: simple, no-database PHP blogging framework

  8. #8
    Settled 4 red convertible dalecosp's Avatar
    Join Date
    Jul 2002
    Location
    Accelerating Windows at 9.81 m/s....
    Posts
    8,378
    Quote Originally Posted by NogDog View Post
    (I won't tell you about the kludge I wrote today to deal with a PHP 5.3 issue. )
    I, for one, will sleep MUCH sounder when a particular domain of our current 5 dozen runs on something that was actually released during the current decade....
    /!!\ mysql_ is deprecated --- don't use it! Tell your hosting company you will switch if they don't upgrade! /!!!\ ereg() is deprecated --- don't use it!

    dalecosp "God doesn't play dice." --- Einstein "Perl is hardly a paragon of beautiful syntax." --- Weedpacket

    Getting Help at All --- Collected Solutions to Common Problems --- Debugging 101 --- Unanswered Posts --- OMBE: Office Machines, Business Equipment

  9. #9
    Senior Member
    Join Date
    Mar 2009
    Posts
    1,153
    Thanks for the replies everyone!

    Quote Originally Posted by dalecosp View Post
    Well, it looks very nice ... seriously. Perhaps it needs this though:

    PHP Code:
    function __construct() {

       if (
    PHP_MAJOR_VERSION 7) {
          throw new 
    VersionMismatchException('PHP Version Insufficient!');
       }


    As for my way of doing it, I probably don't do it enough. Some of our systems go way back to 5.2 days (eek!) and so does the code, and tokens are generally used mostly for AJAX stuff and is based on hashing constants and secrets, server names and timestamps and poems and such-like ...
    Eventually I'd like to add it to Packagist and in doing so put a PHP >= 7 requirement on it. I have finally found the convenience and benefits of using Composer and now I'm not sure I could go without it.

    I couldn't imagine working in 5.2...

    Quote Originally Posted by Derokorian View Post
    ... but for this problem I use an array in session ...

    Actually adding a token and checking a token are handled through a collection class that's pretty indepth but nicely encapsulates all the logic. This way I can add multiple tokens in a single page load as well (IE: login form, register form and forgot password form, which all load in a single request, though register and forgot are hidden until needed).
    I saw solutions that involved an array, but it seemed complicated to me and this solution seemed like it would work better with the least amount of refactoring (we had them across many pages). The above code is admittedly quite simple as I haven't implemented any additional security like expiration date, etc. We had thought adding it for our application but the user base could suffer for it so we decided what we had was still secure (an attacker would have to know the secret anyway, which means we had already lost).

    A general-use collection class (that's abstract) is something else I have been working on, too. I may post it here for feedback in the near future.
    Declare variables, not war.

  10. #10
    Senior Member Derokorian's Avatar
    Join Date
    Apr 2011
    Location
    Denver
    Posts
    2,242
    PHP Code:
     $signed $token.'-'.$hash
    This part seriously concerns me. You're given them the token and the resulting hash, given enough of these combinations a hacker can guess the secret, since its always the same I believe. I would use a separate key, and encrypt that entire string with openssl_encrypt, then when you check you can use this key to decrypt to the string above. I think this would be more secure.

    PHP Code:
     $signed $token.'-'.$hash
     
    $signed openssl_encrypt($signed'AES-256-CBC'ENCRYPT_PASS);

        public static function 
    verify(string $signedToken
        { 
            
    $signedToken openssl_decrypt($signedToken'AES-256-CBC'ENCRYPT_PASS);
            
    $parts explode('-'$signedToken); 
    Sadly, nobody codes for anyone on this forum. People taste your dishes and tell you what is missing, but they don't cook for you. ~anoopmail
    I'd rather be a comma, then a full stop.
    User Authentication in PHP with MySQLi - Don't forget to mark threads resolved - MySQL(i) warning

  11. #11
    Senior Member
    Join Date
    Mar 2009
    Posts
    1,153
    Quote Originally Posted by Derokorian View Post
    This part seriously concerns me. You're given them the token and the resulting hash, given enough of these combinations a hacker can guess the secret, since its always the same I believe. I would use a separate key, and encrypt that entire string with openssl_encrypt, then when you check you can use this key to decrypt to the string above. I think this would be more secure.

    PHP Code:
     $signed $token.'-'.$hash
     
    $signed openssl_encrypt($signed'AES-256-CBC'ENCRYPT_PASS);

        public static function 
    verify(string $signedToken
        { 
            
    $signedToken openssl_decrypt($signedToken'AES-256-CBC'ENCRYPT_PASS);
            
    $parts explode('-'$signedToken); 
    I tried to avoid using something that wasn't part of the SPL. My original implementation used openssl_random_pseudo_bytes(), but then I had to check to make sure the extension was enabled. That is why I switched to using random_bytes() because it's included by default in PHP 7, and there are no checks or exceptions being thrown in the generation of a token.

    Is there some kind of fallback in the SPL that can be used if the OpenSSl extension is not loaded?

    Also in my very cursory testing with openssl_encrypt, you must provide an initialization vector. When I tried to run your code I got a PHP warning (or a notice - I forget which). And from what I can tell you must provide the same initialization vector to decrypt it, which kinda defeats it since it would have to be generated every time and then passed every time, but to pass it every time means it has to be available client-side.

    In any case, I'm not worried about someone "guessing" the secret. Granted, it can be as complex as the developer would like it to be, but in my case it's a string of 100 random characters, whose character set includes every character on the standard keyboard minus the space, single quote, double quote, and back slash. No supercomputer on the planet could crack that in a trillion years.

    I like the idea, however, so if there is a proven fallback in the SPL I see no reason not to implement it.
    Declare variables, not war.

  12. #12
    Senior Member
    Join Date
    Apr 2003
    Location
    Flanders Fields
    Posts
    5,800
    The first thing that occurs to me is that TOKEN_SECRET should probably not be defined as a constant. To define it as a constant suggests that you set one value which does not change. It also smells like Service Locator/Factory patterns to me. I think some kind of dependency injection approach would be better. E.g., pass in $token_secret as a var to a constructor? Or if you really want static methods, assign some value to Token::$token_secret? It would make your class more flexible if a user could choose their own method of defining $token_secret rather than having to define a constant somewhere? E.g., reading it from a config file or a cookie or something.

    Philosophically, and from a security standpoint, the idea that there is one TOKEN_SECRET defined somewhere in a constants file doesn't sound all that secure to me. I don't really know how you are defining TOKEN_SECRET but my guess is that it is consistent from page access to page access. My security muscles have atrophied a bit, but have you considered applying Kerckhoff's principle? I.e., if someone were to read your source, would they gain access to TOKEN_SECRET?

    Also, if you allow any random visitor to see a possibly unlimited number of token-hash combinations, you are badly exposing your system to a known-plaintext attack.

    Perhaps you could provide some very simple usage examples for discussion?
    IMPORTANT: STOP using the mysql extension. Use mysqli or pdo instead.
    World War One happened 100 years ago. Visit Old Grey Horror for the agony and irony.

  13. #13
    Senior Member Derokorian's Avatar
    Join Date
    Apr 2011
    Location
    Denver
    Posts
    2,242
    Quote Originally Posted by sneakyimp View Post
    The first thing that occurs to me is that TOKEN_SECRET should probably not be defined as a constant. To define it as a constant suggests that you set one value which does not change. It also smells like Service Locator/Factory patterns to me. I think some kind of dependency injection approach would be better. E.g., pass in $token_secret as a var to a constructor? Or if you really want static methods, assign some value to Token::$token_secret? It would make your class more flexible if a user could choose their own method of defining $token_secret rather than having to define a constant somewhere? E.g., reading it from a config file or a cookie or something.

    Philosophically, and from a security standpoint, the idea that there is one TOKEN_SECRET defined somewhere in a constants file doesn't sound all that secure to me. I don't really know how you are defining TOKEN_SECRET but my guess is that it is consistent from page access to page access. My security muscles have atrophied a bit, but have you considered applying Kerckhoff's principle? I.e., if someone were to read your source, would they gain access to TOKEN_SECRET?

    Also, if you allow any random visitor to see a possibly unlimited number of token-hash combinations, you are badly exposing your system to a known-plaintext attack.

    Perhaps you could provide some very simple usage examples for discussion?
    The secret absolutely has to be constant from page-load to page load for a given environment. Think of it like your private key file, it never changes but is needed on each ssh to validate the connection is secure. If the token changed, you'd never be able to validate the token given back. As for the plain-text: see my comment about encrypting the whole string, so that you don't have access to the token and the signature.
    Sadly, nobody codes for anyone on this forum. People taste your dishes and tell you what is missing, but they don't cook for you. ~anoopmail
    I'd rather be a comma, then a full stop.
    User Authentication in PHP with MySQLi - Don't forget to mark threads resolved - MySQL(i) warning

  14. #14
    Senior Member
    Join Date
    Apr 2003
    Location
    Flanders Fields
    Posts
    5,800
    Quote Originally Posted by Derokorian View Post
    The secret absolutely has to be constant from page-load to page load for a given environment.
    I realize the goal is to allow multi-tab and/or AJAX requests and or SPAs so there is some desire to avoid having the token change too often. This does not mean that we have to define the value as a constant in code using define. I would say that any value that depends on the current user's id should not be defined in a constant. I'd also point out that password hashing best practices dictate the use of a salt that is different for each and every password. I wouldn't claim to be any sort of supreme expert in this kind of thing, but I would imagine that a single TOKEN_SECRET for one's site might not be ideal -- especially if you could improve matters by storing a distinct $token_secret for each session.

    Quote Originally Posted by Derokorian View Post
    Think of it like your private key file, it never changes but is needed on each ssh to validate the connection is secure. If the token changed, you'd never be able to validate the token given back.
    If I'm not mistaken, the ssh protocol works by authenticating one's private key initially but then another per-session key is generated for the duration of the ssh session. Also, ssh keys are quite large -- typically 2048 or more bits these days.

    Quote Originally Posted by Derokorian View Post
    As for the plain-text: see my comment about encrypting the whole string, so that you don't have access to the token and the signature.
    I think this is an improvement, but I would want to see details of how bonesnap intends to use his Token class. So far it only does two things: 1) generate a random token and hash it using TOKEN_SECRET defined as a constant somewhere and return token-hash and then 2) verify that a given token-hash is hashed the same way he hashes things. How are these tokens used? Does every AJAX request generated on a page use the exact same token-hash string? How is TOKEN_SECRET generated? If it's just a fixed value in a constants file and he's using that class above, then any valid token-hash will be valid forever (or until he changes his TOKEN_SECRET in his source code) and there's nothing to stop Mallory from getting one valid token-hash value and using it to forge requests for Alice, Bob, or whoever. We'd have to see more specifics to determine how good it is.
    IMPORTANT: STOP using the mysql extension. Use mysqli or pdo instead.
    World War One happened 100 years ago. Visit Old Grey Horror for the agony and irony.

  15. #15
    Senior Member
    Join Date
    Mar 2009
    Posts
    1,153
    Quote Originally Posted by sneakyimp View Post
    The first thing that occurs to me is that TOKEN_SECRET should probably not be defined as a constant. To define it as a constant suggests that you set one value which does not change. It also smells like Service Locator/Factory patterns to me. I think some kind of dependency injection approach would be better. E.g., pass in $token_secret as a var to a constructor? Or if you really want static methods, assign some value to Token::$token_secret? It would make your class more flexible if a user could choose their own method of defining $token_secret rather than having to define a constant somewhere? E.g., reading it from a config file or a cookie or something.

    Philosophically, and from a security standpoint, the idea that there is one TOKEN_SECRET defined somewhere in a constants file doesn't sound all that secure to me. I don't really know how you are defining TOKEN_SECRET but my guess is that it is consistent from page access to page access. My security muscles have atrophied a bit, but have you considered applying Kerckhoff's principle? I.e., if someone were to read your source, would they gain access to TOKEN_SECRET?

    Also, if you allow any random visitor to see a possibly unlimited number of token-hash combinations, you are badly exposing your system to a known-plaintext attack.

    Perhaps you could provide some very simple usage examples for discussion?
    I'll start with a simple usage example:

    1. Page loads and a token is generated using Token::generate().
    2. It's printed in a hidden input field (or something similar).
    3. An AJAX call is made. The value in the hidden input field is passed as part of the request.
    4. In the PHP file on the server-side, the token is verified using Token::verify($thePostedToken).
    5. If they don't match, throw an exception or error out or do whatever you want.
    6. If they do match, then proceed.

    So the intent is to keep it constant - hence the use of the constant. The developer can define it anyway they want to.

    Kerckhoff's Principle honestly doesn't make much sense to me. It sounds great but to me isn't grounded in reality for a very simple reason: if an attacker has access to my source code then it's game over. There's no security in the world that can handle that. Furthermore if that were to ever happen, the last thing I care about is my token. I don't give a rodent's rear at that point - there are far larger fish to fry. You know what else I have stored away in my source code? Database credentials, PHPMailer credentials, etc. I have bigger problems than a token.

    Quote Originally Posted by sneakyimp
    I would say that any value that depends on the current user's id should not be defined in a constant.
    The token secret has nothing to do with the user's ID.

    Quote Originally Posted by sneakyimp
    I'd also point out that password hashing best practices dictate the use of a salt that is different for each and every password.
    This has nothing to do with passwords so I am not sure why it was mentioned. Also, each hash will be different anyway since the token will be different.

    Quote Originally Posted by sneakyimp
    How is TOKEN_SECRET generated?
    However the developer would like. For me, I used KeePass and generated a random 100 character string.

    Quote Originally Posted by sneakyimp
    If it's just a fixed value in a constants file and he's using that class above, then any valid token-hash will be valid forever (or until he changes his TOKEN_SECRET in his source code) and there's nothing to stop Mallory from getting one valid token-hash value and using it to forge requests for Alice, Bob, or whoever.
    In my specific example, yes, but as I mentioned in my original post you could harden it by including an expiration during hashing or a user's ID, etc. That is left up to the developer.
    Declare variables, not war.

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •