Token Best Practices

Token Best Practices

Here are some basic considerations to keep in mind when using tokens:

  • Keep it secret. Keep it safe: The signing key should be treated like any other credential and revealed only to services that need it.

  • Do not add sensitive data to the payload: Tokens are signed to protect against manipulation and are easily decoded. Add the bare minimum number of claims to the payload for best performance and security.

  • Give tokens an expiration: Technically, once a token is signed, it is valid forever—unless the signing key is changed or expiration explicitly set. This could pose potential issues so have a strategy for expiring and/or revoking tokens.

  • Embrace HTTPS: Do not send tokens over non-HTTPS connections as those requests can be intercepted and tokens compromised.

  • Consider all of your authorization use cases: Adding a secondary token verification system that ensures tokens were generated from your server may be necessary to meet your requirements.

  • Store and reuse: Reduce unnecessary roundtrips that extend your application's attack surface, and optimize plan token limits (where applicable) by storing access tokens obtained from the authorization server. Rather than requesting a new token, use the stored token during future calls until it expires. How you store tokens will depend on the characteristics of your application: typical solutions include databases (for apps that need to perform API calls regardless of the presence of a session) and HTTP sessions (for apps that have an activity window limited to an interactive session). For an example of server-side storage and token reuse, see Token Storage.

Tokens vs. Cookies

Typically, single-page apps (such as React, Vue, and AngularJS + Node), native mobile apps (such as iOS and Android), and web APIs (written in Node, Ruby, ASP.NET, or a mix of those) benefit most from token-based authentication. Traditional, server-side web applications have traditionally used cookie-based authentication.

Token-based authentication is implemented by generating a token when the user authenticates and then setting that token in the Authorization header of each subsequent request to your API. You want that token to be something standard, like JSON web tokens since you will find libraries in most of the platforms and you don't want to do your own crypto.

With both approaches, you can get the same amount of information from the user. That's controlled by the scope parameter sent in the login request (either using the Lock, our JavaScript library or a plain link). The scope is a parameter of the .signin({scope: 'openid name email'}) method which ends up being part of the query string in the login request.

By default, we use scope=openid in token-based authentication to avoid having a huge token. You can control any standard OpenID Connect (OIDC) claims that you want to get in the token by adding them as scope values. For example, scope=openid name email family_name address phone_number. To learn more, see Standard Claims on openid.net.

You can mix token-based authentication with cookie-based authentication. Take into account that cookies will work just fine if the web app and the API are served from the same domain, so you might not need token based authentication. If you need to, we also return a JWT on the web app flow. Each of our SDKs will do it differently. If you want to call your APIs from JavaScript(instead of using the existing cookie), then you have to set the access tokens using Web Workers or JavaScript closures to handle token transmissions and storage. To learn more, read the Browser in-memory scenarios section of our Token Storage page.

Refresh token usage

You can only get a Refresh token if you are implementing the following flows:

If you limit offline access to your API, a safeguard configured via the Allow Offline Access switch at Auth0 Dashboard > Applications > APIs > Settings, Auth0 will not return a Refresh Token for the API (even if you include the offline_access scope in your request).

Rules will run for the refresh token exchange. To execute special logic, you can look at the context.protocol property in your rule. If the value is oauth2-refresh-token, then the rule is running during the exchange.

When trying to get a refresh token, the audience parameter is not available on the Rules context object. If you receive an error when attempting to add the audience parameter, verify that you do not have it set on the token.

If you try to do a redirect with context.redirect, the authentication flow will return an error.

If you have added custom claims to your tokens using a rule, the custom claims will appear in new tokens issued when using a refresh token for as long as your rule is in place. Although new tokens do not automatically inherit custom claims, rules run during the refresh token flow, so the same code will be executed. This allows you to add or change custom claims in newly-issued tokens without forcing previously-authorized applications to obtain a new refresh token.

Refresh token limits

Auth0 limits the amount of active refresh tokens to 200 tokens per user per application. This limit only applies to active tokens. If the limit is reached and a new refresh token is created, the system revokes and deletes the oldest token for that user and application. Revoked tokens and expired tokens do not count against the limit.

Automated tests

Refresh tokens accumulate due to automated tests and are generally used for the test lifetime. To avoid a token stockpile subject to refresh token limits, you can use the Auth0 Management API to remove unnecessary refresh tokens.

  1. Create a user with Management API. You will use this user for testing.

  2. The response returns a user_id that you need to persist during tests to be used later.

  3. Once tests are complete, delete the user through Management API. When the test user is deleted, the associated artifacts are also removed, including refresh tokens.

If you want to keep the test user for future testing:

  1. List the user’s refresh tokens using Management API's device credential endpoint. The endpoint will return a maximum of 1000 tokens without specific order regardless of accumulated tokens or the use of pagination.

  2. Delete those credentials using the DELETE method.

  3. If the user has more than 1k tokens, repeat listing and deleting tokens until no more tokens left for the user.

Configure Expiring Refresh Tokens

When users log into your application with Auth0, and when the offline_access is requested in the authorization request, a new refresh token is issued to the user. In the case users log out and in again with the same device, a new refresh token is issued. Depending on how your application stores and uses refresh tokens, the old refresh token from the first login might become obsolete, and your application will most likely use the new refresh tokens if both tokens are issued with the same audience. To learn more, read Token Storage.

To avoid accumulating obsolete refresh tokens, even though the refresh token limit removes the oldest token first, we recommend you configure refresh token expiration. Both rotating and non-rotating (or reusable) refresh tokens can be configured to expire with either idle or absolute expiry values. Both expiration values help remove tokens that are not in active use and avoid accumulating tokens for the user. To learn more, read Configure Refresh Token Expiration.

JWT validation

We strongly recommend that you use middleware or one of the existing open source third-party libraries to parse and validate JWTs. At JWT.io, you can find libraries for various platforms and languages, such as .NET, Python, Java, Ruby, Objective-C, Swift, and PHP.

Signing algorithms

The algorithm used to sign tokens issued for your application or API. A signature is part of a JWT and is used to verify that the sender of the token is who it says it is and to ensure that the message wasn't changed along the way. To learn more about JWTs, read JSON Web Tokens. To learn more about signatures, read JSON Web Token Structure.

You can select from the following signing algorithms:

  • RS256 (RSA Signature with SHA-256): An asymmetric algorithm, which means that there are two keys: one public key and one private key that must be kept secret. Auth0 has the private key used to generate the signature, and the consumer of the JWT retrieves a public key from the Metadata endpoints provided by Auth0 and uses it to validate the JWT signature.

  • HS256 (HMAC with SHA-256): A symmetric algorithm, which means that there is only one private key that must be kept secret, and it is shared between the two parties. Since the same key is used both to generate the signature and to validate it, care must be taken to ensure that the key is not compromised. This private key (or secret) is created when you register your Application (Client Secret) or API (Signing Secret) and choose the HS256 signing algorithm.

The most secure practice, and our recommendation, is to use RS256 because:

  • With RS256, you are sure that only the holder of the private key (Auth0) can sign tokens, while anyone can check if the token is valid using the public key.

  • With RS256, you can request a token that is valid for multiple audiences.

  • With RS256, if the private key is compromised, you can implement key rotation without having to re-deploy your application or API with the new secret (which you would have to do if using HS256).

  • With HS256, if the secret key is compromised you would have to redeploy the API with the new secret.

Signing keys

It's good practice to assume that multiple signing keys could be present in your JWKS. This may seem unnecessary since the Auth0 JWKS endpoint typically contains a single signing key; however, multiple keys can be found in the JWKS when rotating signing certificates.

We recommend that you cache your signing keys to improve application performance and avoid running into rate limits, but you will want to make sure that if decoding a token fails, you invalidate the cache and retrieve new signing keys before trying only one more time.

Learn more