Back to Shorts

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...

1 views2 min read
Workflow

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 Token

The access token is short-lived.

It is used to access protected APIs.

GET /api/profile
Authorization: Bearer access_token

Because the access token expires quickly, the damage is smaller if it gets stolen.

Example expiration:

Access token: 15 minutes

The 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_token

Example expiration:

Refresh token: 7 days or 30 days

The 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 client

A common response:

JSON
{
  "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_token

The 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:

JSON
{
  "message": "Access token expired"
}

Refresh Flow

When the access token expires, the frontend calls the refresh endpoint.

POST /auth/refresh

The 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 token

Then the server returns a new access token.

JSON
{
  "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 account

If 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_at

Do 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 out

Example response behavior:

Set-Cookie: refresh_token=; Max-Age=0

After 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 revoked

The 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.

0

Comments

Join the discussion for this short.

GU

Join the discussion

Sign in first, then write a comment or reply to an existing thread.

Login required

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.

Readers need to sign in with GitHub before they can post a comment or reply.
Loading comments...

Arya Dipanegara

Personal developer website for writing, projects, short notes, and practical software work.

Stay in touch

No newsletter flow here. Email works best for project ideas, collaborations, or quick questions.

Email me

© 2026 Arya Dipanegara. All rights reserved.