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