Auth 101: Token Based Authentication

Overview
There are a number of ways to implement secure authentication and authorization (some strategies are usually combined depending on application needs):
Token based authentication
OAuth using Single Sign-On (SSO) or trusted 3rd party identity providers like Google and Microsoft
Session based authentication - server managed sessions
Certificate and hardware based authentication using devices like YubiKeys and smart cards
This article goes over the token based authentication strategy, where a typical authentication flow looks like:
Client presents a login form to a user, user fills in their crendentials.
Client sends these credentials to the server.
Server verifies credentials, generates a token and sends the token back to the client.
Client stores the token and adds the token to subsequent requests to protected endpoints on the server
Server verifies the token validity and executes the request.
My AQs
Before we continue, I had some questions in mind (that you might have too). I’ll try to answer them in the article 😅
How do we send the initial credentials to the server securely? Wouldn’t a snooper look at the network request and use the credentials to generate their own token? only send data over https, add Multi-Factor Authentication.
How do we store passwords securely in the database? hash and salt passwords before storing them in the database.
What is a JWT token? It is a medium used to transmit data between 2 parties that can be verified using digital signatures
How long should a token live? depends on the app needs, but always set expiry time.
How should we store tokens on the client? use in memory storage with a token refreshing strategy
What is a refresh token? a token that lets us renew our expired tokens.
What are some security vulnerabilities around token authentication? xss and csrf attacks, man-in-the middle attacks, non-expiring tokens, unsanitized data.
How do we avoid bad UX with expiring tokens? Use silent authentication techniques.
Token based authentication
JWT Standard
JSON Web Token (JWT) is an open standard that defines a JWT Token as a compact safe medium of transferring data between two parties where the data is presented in a JSON object that is encoded and digitally signed.
A JWT is made up of 3 encoded sections:
Header: contains metadata about the token, it tells us the token type and the algorithm used to sign the token
Payload: contains the claims (information about the entity in question and additional data). Claims can be:
Registered: exp (expiration time), sub (subject)
Public: information we want to transmit e.g. name, roles
- Signature: used to verify token integrity and authenticity. A signature is created using the algorithm specified in the header to sign a combination of the encoded header, the encoded payload and a secret. The secret helps us verify that the token hasn’t been tampered with and should be kept somewhere safe (use env variables)
Here is a decoded token structure from https://jwt.io/
Authentication flow in detail
Step 1: Sign up a user
On the client:
Use a form to collect user details
Validate and sanitize the data on the frontend (some checks include checking email format, empty strings, password strength)
Consolidate the data in a JSON object and send it to a signup endpoint on the server.
On the backend, when we receive data:
Validate and sanitize the data
hash and salt the password using a library like bcrypt - we should never store passwords as plain texts
check for user duplicates if needed and store user details in the database
Step 2: Login a user
On the client:
Collect user credentials, sanitize the data if needed (some checks include checking email format, empty strings).
Consolidate the data in a JSON object and send it to a login endpoint on the backend server.
On the backend, when we receive data:
Verify user exists in database.
Verify the password hash stored in the database matches the password provided.
Step 3: Generate tokens
Using a jwt library generate two JWT tokens, an access token and a refresh token.
Access token: should be short lived, with expiry set to a few minutes or hours. This token contains the sensitive information the frontend client needs to know e.g. user id, user roles or permissions.
Refresh token: is a longer lived token, with expiry set to a few days or months. This token is used to generate new access tokens when the current ones expire.
Step 4: Store the tokens
The access token:
Server sends back the access token to the client as a JSON response
Client stores the token in memory, in React we can use state, context or memory.
🛑Storing access tokens in memory is safer than local storage. Tokens in memory are vulnerable to XSS attacks if the application has JavaScript injection vulnerabilitiesClient then includes the token in the Authorization header for every subsequent request to protected endpoints.
Store the refresh token in a HTTP-only cookie:
Server sets the cookie in the response header
Client receives the token and stores the cookie in the browser's cookie storage.
The cookie is then automatically included in every subsequent request to the server.
A HTTP-only cookie is a type of cookie designed to be accessed only by the server via HTTP requests.
It is inaccessible to client-side javascript scripts which mitigates against xss attacks.
If we set the right security configurations (e.g. setting same origin) we can mitigate against csrf attacks. Additionally, CSRF tokens or
SameSite
cookies can protect against CSRF.The cookie persists between browser sessions for the same browser profile. Opening an incognito tab or a new browser profile will need a new auth session.
In summary, here are some differences between the 2 tokens
Access Token | Refresh Token | |
Purpose | Used to transmit sensitive information between client and server | Used to obtain new access tokens when the current one expires |
Lifespan | Short lived (minutes to hours) | Long lived (days to months) |
How it is used | Sent with each request to protected resources (in the Authorization header) | Only sent to a specific endpoint when we need to generate new tokens |
Where it is stored | Stored on the client in short term storage e.g. memory, state or context | Stored in a HTTP-only cookie |
Data stored | Sensitive data: user id, roles, permissions, name etc. | Minimal data: user id, token id |
Security risk | Limits misuse because it expires quickly, but contains sensitive info | More sensitive because new tokens can be generated if exposed |
Step 5: Using the token
The access token:
Client sets the token in the Authorization header of the request to the server.
Server gets the token from the header, verifies the signature and expiry, decodes the token, validates expiry, verifies user permissions and then executes the request.
The refresh token:
Client makes a request to a refresh token endpoint.
Server obtains the refresh token from the HTTP-only cookie, verifies the signature and expiry, and decodes the token.
Server then uses the refresh token info to generate a new access token which is returned to the client in the JSON response (we can go a step further and blacklist the older refresh token so that it isn’t re-used).
Client stores new access token and the req/res cycle continues.
How can we avoid bad UX if the token expires too quickly?
Use silent authentication techniques:
- We could explicitly lookout for a token expiry error on the client, and make a refresh token request, then use the token returned to retry the failed request. All this could be gracefully handled so the user never knows.
- Or we can keep track of token expiry on the client so that we can make pre-emptive refresh requests. But this strategy could introduce performance issues.
In summary:
On sign up or login, generate 2 tokens, a short-lived access token and a longer-lived refresh token.
Use the access token to transmit the needed data (e.g. user id, roles, permissions etc.) between client and server. Store the token in client temporary storage like memory or state. Send it via the Authorization header for protected endpoints.
Use the refresh token to generate new access tokens on expiration through a refresh token endpoint. Store the token in a http-only cookie.
Secure best practices
Sanitize and validate input data in all relevant application layers.
Hash and salt passwords before storing them in the database.
Implement Role Based Access Control (RBAC).
Send requests via https to avoid man-in-the-middle attacks and network eavesdropping (consider using TLS certificates).
Implement Content Security Policy (CSP) to prevent certain attacks such as Cross-Site Scripting (XSS) and data injection attacks.
Implement 2FA or MFA (Multi-Factor Authentication).
Use OAuth2 providers like Google and Microsoft as identity providers, they have compliant libraries that make auth easier. OAuth builds on top of the token based strategy.
So, did I answer all (y)our token authentication based questions?
Subscribe to my newsletter
Read articles from Shalon N. Ngigi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
