Workflow
Why Real Apps Use Access Token and Refresh Token
Access Token and Refresh Token Flow Most simple login tutorials only return one JWT token. It works, but it is not always ideal for real applications. A bett...
Access Token and Refresh Token Flow
Most simple login tutorials only return one JWT token.
It works, but it is not always ideal for real applications.
A better approach is to separate the token into two parts:
Access Token
Refresh TokenThe access token is short-lived.
It is used to access protected APIs.
GET /api/profile
Authorization: Bearer access_tokenBecause the access token expires quickly, the damage is smaller if it gets stolen.
Example expiration:
Access token: 15 minutesThe refresh token is long-lived.
It is used only to request a new access token when the old one expires.
POST /auth/refresh
Cookie: refresh_tokenExample expiration:
Refresh token: 7 days or 30 daysThe important rule is simple:
Access token is for accessing APIs.
Refresh token is for getting new access tokens.Login Flow
When the user logs in successfully, the server creates both tokens.
1. User sends email and password
2. Server validates the credentials
3. Server creates access token
4. Server creates refresh token
5. Server sends both tokens to the clientA common response:
{
"accessToken": "short_lived_token"
}And the refresh token is usually stored in an HTTP-only cookie:
Set-Cookie: refresh_token=long_lived_token;
HttpOnly;
Secure;
SameSite=Lax;Why cookie?
Because JavaScript cannot read an HTTP-only cookie.
That makes it safer than storing refresh tokens in localStorage.
Accessing Protected API
When the frontend wants to access private data, it sends the access token.
GET /api/me
Authorization: Bearer access_tokenThe backend checks:
Is the access token valid?
Is it expired?
Does the user still exist?If valid, the request continues.
If expired, the API returns:
{
"message": "Access token expired"
}Refresh Flow
When the access token expires, the frontend calls the refresh endpoint.
POST /auth/refreshThe backend reads the refresh token from the HTTP-only cookie.
Then it checks the database.
Find refresh token in database
Check if token is valid
Check if token is not expired
Check if token is not revoked
Create new access tokenThen the server returns a new access token.
{
"accessToken": "new_short_lived_token"
}The user stays logged in without entering email and password again.
Why Store Refresh Token in Database?
Refresh tokens should be stored in the database so they can be revoked.
For example:
User logout
Password changed
Token stolen
Device removed
Admin disables accountIf refresh token only exists as a JWT and is never stored, it becomes harder to revoke.
A better structure:
refresh_tokens
- id
- user_id
- token_hash
- expires_at
- revoked_at
- created_atDo not store the raw refresh token.
Store the hashed version.
Raw token goes to client.
Hashed token goes to database.So if the database leaks, the real refresh token is not directly exposed.
Logout Flow
Logout should not only delete the token from the frontend.
The backend should also revoke the refresh token.
1. User clicks logout
2. Frontend calls /auth/logout
3. Backend revokes refresh token in database
4. Backend clears refresh token cookie
5. User is logged outExample response behavior:
Set-Cookie: refresh_token=; Max-Age=0After logout, the old refresh token cannot be used again.
Simple Mental Model
Access token:
Short life
Used often
Sent to APIs
Not stored in database usually
Refresh token:
Long life
Used rarely
Used only to refresh access token
Stored hashed in database
Can be revokedThe most important part:
Access token gives access.
Refresh token keeps the session alive.This separation makes authentication safer and easier to control.
Helpful short? Tap like.
Comments
Join the discussion for this short.
Join the discussion
Sign in first, then write a comment or reply to an existing thread.
Sign in with GitHub to start a new comment. After that, this area will become active for writing.
Your GitHub identity will be used for comment attribution.