OAuth Token Refresh

Keep your access alive with automatic token refresh in 8 minutes

8 mins
Beginner

Access tokens expire after 1 hour. This guide shows you how to automatically refresh them to keep your integration running smoothly.

Why Do Tokens Expire?

Security. Short-lived access tokens limit damage if they're stolen. Even if someone intercepts your access token, it only works for 1 hour.

Two types of tokens:

Token Type Lifespan Purpose
Access Token 1 hour Make API calls
Refresh Token No expiration Get new access tokens

Think of it like a hotel key card:

  • Access token = Your room key (expires when you check out)
  • Refresh token = Your ID at the front desk (get a new room key anytime)

What Happens When Tokens Expire

sequenceDiagram participant App as Your Application participant API as DoorFlow API App->>API: GET /api/3/people
Authorization: Bearer [access_token] Note over API: Token expired! API-->>App: 401 Unauthorized
"The access token expired" Note over App: Need to refresh the token

When your access token expires, the API returns 401 Unauthorized. This is your signal to refresh.

The Token Refresh Flow

Here's the complete process when a token expires:

sequenceDiagram participant App as Your Application participant API as DoorFlow API participant DB as Your Database Note over App,DB: 1. Access token has expired App->>API: GET /api/3/people
Authorization: Bearer [expired_token] API-->>App: 401 Unauthorized Note over App: 2. Get refresh token from storage App->>DB: Get refresh_token DB-->>App: refresh_token Note over App: 3. Request new tokens App->>API: POST /oauth/token
grant_type=refresh_token
refresh_token=...
client_id=...
client_secret=... Note over API: Validates refresh token API-->>App: 200 OK
New access_token
New refresh_token Note over App: 4. Store BOTH new tokens App->>DB: Store new access_token
Store new refresh_token Note over App: 5. Retry original request App->>API: GET /api/3/people
Authorization: Bearer [new_token] API-->>App: 200 OK
People data

How to Refresh a Token

Request:

bash
curl -X POST "https://api.doorflow.com/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=YOUR_REFRESH_TOKEN" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

Parameters

grant_type - Always "refresh_token"
refresh_token - Your current refresh token (from storage)
client_id - Your OAuth app's client ID
client_secret - Your OAuth app's client secret

Response:

json
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def502003d8f9a7b2c1e4d5f6g7h8i9j0k...",
  "scope": "account.person",
  "created_at": 1704070800
}

Critical: You receive BOTH:

  • New access token - Use this for API calls
  • New refresh token - Store this and use it for the next refresh

The old refresh token is now invalid. Always use the newest refresh token.

Implementation Approach 1: Reactive Refresh

Strategy: Wait for 401, then refresh and retry.

flowchart TD Start([Need to call API]) --> Call[Make API request
with access_token] Call --> Check{Response
status?} Check -->|200 OK| Success([Return data]) Check -->|401 Unauthorized| Refresh[Use refresh_token to get
new access_token] Check -->|Other error| Error([Handle error]) Refresh --> Store[Store new access_token
and new refresh_token] Store --> Retry[Retry original API request
with new access_token] Retry --> Success

When to use

Simpler to implement
Good for low-traffic integrations
Acceptable to have occasional failed requests

Tradeoff

First API call after expiration fails (then succeeds on retry)

Implementation Approach 2: Proactive Refresh

Strategy: Check expiration time before making calls. Refresh early if needed.

flowchart TD Start([Need to call API]) --> Load[Load access_token
and expires_at from storage] Load --> Check{Token expires
in next
5 minutes?} Check -->|Yes| Refresh[Use refresh_token to get
new access_token] Check -->|No| UseExisting[Use existing access_token] Refresh --> Store[Store new access_token
and new refresh_token] Store --> Call[Make API request
with new access_token] UseExisting --> Call Call --> Success([Return data])

When to use

High-traffic integrations
User-facing applications (no failed requests)
Background jobs with strict timing

Tradeoff

More complex (track expiration time)
Still need 401 handling as fallback

How to track expiration:

When you store tokens, also calculate and store the expiration time:

json
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "def502003d8f9a7b2c1e4d5f6g7h8i9j0k...",
  "expires_at": 1704074400
}

Calculation

API returns expires_in: 3600 (seconds)
Your code: expires_at = current_time + 3600
Before API calls: Check if current_time + 300 (5 min buffer) > expires_at

When Token Refresh Fails

Not all refresh attempts succeed. Here's how to handle failures:

flowchart TD Start([Refresh token request]) --> Post[POST /oauth/token] Post --> Check{Response
status?} Check -->|200 OK| Success[Store new tokens] Check -->|400 Bad Request| ParseError{Error type?} Success --> Done([Continue with API calls]) ParseError -->|invalid_grant| InvalidToken[Refresh token is
expired or revoked] ParseError -->|invalid_client| WrongCreds[Client ID/secret
is incorrect] ParseError -->|unauthorized_client| NotAuth[OAuth app settings
problem] InvalidToken --> Reauth[Clear stored tokens
Redirect user to
OAuth authorization flow] WrongCreds --> FixCreds([Check your
client credentials]) NotAuth --> CheckSettings([Check OAuth app
configuration]) Reauth --> End([User must reconnect])

Common errors:

Error Meaning Solution
invalid_grant Refresh token expired or revoked User must re-authorize your app
invalid_client Client ID or secret wrong Check your OAuth app credentials
unauthorized_client OAuth app not authorized for this grant type Check OAuth app settings in DoorFlow

Most common: invalid_grant means the user revoked your app's access or the refresh token expired. Clear stored tokens and send the user through OAuth authorization again.

Storing Tokens Securely

Security requirements

Encrypt refresh tokens in your database
Never log tokens (access or refresh)
Clear tokens on logout
Use HTTPS only for all API calls
Store tokens per user (never share tokens between users)

What to store:

json
{
  "user_id": 12345,
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "ENCRYPTED:def502003d8f9a7b2c1e4d5f6g7h8i9j0k...",
  "expires_at": 1704074400,
  "scope": "account.person account.channel.readonly"
}

Critical: Refresh tokens are long-lived and powerful. If someone steals a refresh token, they can access the API indefinitely. Always encrypt them.

Testing Your Implementation

How to test token refresh

Manually expire a token in your database:

  • Set expires_at to a time in the past
  • Example: expires_at = current_time - 1000
Set expires_at to a time in the past
Example: expires_at = current_time - 1000

Make an API call - Should trigger your refresh logic

Verify:

  • API call succeeds
  • New tokens stored in database
  • New expires_at is ~1 hour in the future
API call succeeds
New tokens stored in database
New expires_at is ~1 hour in the future

Test error handling

Test invalid refresh token:

  • Change the refresh token in your database to "invalid_token"
  • Make an API call
  • Verify user is sent through OAuth flow again
Change the refresh token in your database to "invalid_token"
Make an API call
Verify user is sent through OAuth flow again

Test network errors:

  • Temporarily break network connection
  • Make an API call
  • Verify appropriate error message shown to user
Temporarily break network connection
Make an API call
Verify appropriate error message shown to user

Complete Flow Example

Here's everything together - from initial OAuth through token refresh:

sequenceDiagram participant User participant App as Your Application participant API as DoorFlow API participant DB as Your Database Note over User,DB: Initial OAuth Authorization User->>App: Clicks "Connect DoorFlow" App->>API: Redirect to /oauth/authorize User->>API: Approves access API->>App: Redirect with code App->>API: POST /oauth/token
(exchange code) API-->>App: access_token + refresh_token App->>DB: Store both tokens + expires_at Note over User,DB: Making API Calls (token valid) App->>DB: Get access_token DB-->>App: access_token App->>API: GET /api/3/people
Bearer [access_token] API-->>App: 200 OK (people data) Note over User,DB: 1 Hour Later (token expired) App->>DB: Get access_token DB-->>App: access_token (expired) App->>API: GET /api/3/people
Bearer [expired_token] API-->>App: 401 Unauthorized Note over App: Token expired - refresh it App->>DB: Get refresh_token DB-->>App: refresh_token App->>API: POST /oauth/token
grant_type=refresh_token API-->>App: New access_token + refresh_token App->>DB: Store new tokens App->>API: GET /api/3/people
Bearer [new_token] API-->>App: 200 OK (people data)

Quick Decision Guide

Which refresh strategy should I use?

flowchart TD Start{What type of
integration?} Start -->|Background job
runs every few hours| Reactive[Use Reactive Refresh
Wait for 401, then refresh] Start -->|User-facing web app
Real-time updates| Proactive[Use Proactive Refresh
Check expiration before calls] Start -->|Mobile app| Proactive Start -->|Webhook handler
Low traffic| Reactive Reactive --> Simple[Simpler to implement
Occasional 401 is fine] Proactive --> Complex[More complex
No failed requests]

When do I need to re-authorize the user?

Only when refresh token is invalid:

  • User revoked your app's access
  • OAuth app credentials changed
  • OAuth app was deleted
  • Refresh token manually deleted

Regular expiration of access tokens does NOT require re-authorization.

Common Mistakes

1. Not storing the new refresh token

Each refresh gives you a NEW refresh token. The old one is invalid.

Wrong

Refresh token → Get new access token
Store new access token
Keep using old refresh token ❌
Refresh token → Get new access token AND new refresh token
Store BOTH new tokens ✓

2. Not handling refresh failures

Refresh can fail. Always have a fallback to re-authorize.

3. Logging tokens

Never log access or refresh tokens. Use [REDACTED] in logs.

4. Storing tokens in localStorage (web apps)

Use secure, httpOnly cookies or server-side sessions. localStorage is vulnerable to XSS attacks.

5. Sharing tokens between users

Each user needs their own tokens. Never reuse tokens across different users.

Quick Reference

Refresh a token:

bash
POST https://api.doorflow.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
refresh_token=YOUR_REFRESH_TOKEN
client_id=YOUR_CLIENT_ID
client_secret=YOUR_CLIENT_SECRET

Response

access_token - Use for API calls (1 hour lifetime)
refresh_token - Store for next refresh (replaces old one)
expires_in - 3600 (seconds)

When to refresh

Reactive: When you get 401 Unauthorized
Proactive: 5 minutes before token expires

What to store

access_token
refresh_token (encrypted)
expires_at (current time + expires_in)
scope

Security rules

Encrypt refresh tokens
Never log tokens
HTTPS only
Clear tokens on logout

Next Steps

Now that you understand token refresh:

  • [OAuth Authorization Flow] - See where refresh tokens come from
  • [Choosing the Right Scopes] - Select appropriate permissions
  • [Error Handling] - Handle all API error scenarios
  • [Common Workflows] - See complete integration examples