Signed tokens vs. session-based tokens
Page 1 of 3 123 LastLast
Results 1 to 15 of 40

Thread: Signed tokens vs. session-based tokens

  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,522
    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,261
    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

  4. #4
    Senior Member Derokorian's Avatar
    Join Date
    Apr 2011
    Location
    Denver
    Posts
    2,261
    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

  5. #5
    Settled 4 red convertible dalecosp's Avatar
    Join Date
    Jul 2002
    Location
    Accelerating Windows at 9.81 m/s....
    Posts
    8,522
    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

  6. #6
    Senior Member Derokorian's Avatar
    Join Date
    Apr 2011
    Location
    Denver
    Posts
    2,261
    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

  7. #7
    High Energy Magic Dept. NogDog's Avatar
    Join Date
    Aug 2006
    Location
    Ankh-Morpork
    Posts
    14,879
    (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,522
    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,261
    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
    Apr 2003
    Location
    Flanders Fields
    Posts
    5,833
    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.

  12. #12
    Senior Member Derokorian's Avatar
    Join Date
    Apr 2011
    Location
    Denver
    Posts
    2,261
    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

  13. #13
    Senior Member
    Join Date
    Apr 2003
    Location
    Flanders Fields
    Posts
    5,833
    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.

  14. #14
    Pedantic Curmudgeon Weedpacket's Avatar
    Join Date
    Aug 2002
    Location
    General Contact Unit "Coping Mechanism"
    Posts
    22,533
    Quote Originally Posted by sneakyimp
    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.
    Public-key encryption/decryption like RSA is slow with a lot of extra back-and-forth. So instead of using it for actual communication, a symmetric cipher (like AES) is used instead. The hangup with using a symmetric cipher though is that both parties have to have the same key. How can one party generate such a key and communicate it to the other without it being snooped along the way ... oh. Right.
    THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER
    FAQs! FAQs! FAQs! Most forums have them!
    Search - Debugging 101 - Collected Solutions - General Guidelines - Getting help at all

  15. #15
    Senior Member
    Join Date
    Apr 2003
    Location
    Flanders Fields
    Posts
    5,833
    Quote Originally Posted by Weedpacket View Post
    Public-key encryption/decryption like RSA is slow with a lot of extra back-and-forth. So instead of using it for actual communication, a symmetric cipher (like AES) is used instead. The hangup with using a symmetric cipher though is that both parties have to have the same key. How can one party generate such a key and communicate it to the other without it being snooped along the way ... oh. Right.
    This limits the use of one's precious private key to the initial handshake, thereby limiting its exposure to attack (i.e., it won't be used over and over and over again in a long conversation). Furthermore, if one session key is somehow cracked, the attacker only has insight into that one session, and not other sessions.
    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.

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
  •