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
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:
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:
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:
{
"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.
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
Tradeoff
Implementation Approach 2: Proactive Refresh
Strategy: Check expiration time before making calls. Refresh early if needed.
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
Tradeoff
How to track expiration:
When you store tokens, also calculate and store the expiration time:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "def502003d8f9a7b2c1e4d5f6g7h8i9j0k...",
"expires_at": 1704074400
}
Calculation
expires_in: 3600 (seconds)
expires_at = current_time + 3600
current_time + 300 (5 min buffer) > expires_at
When Token Refresh Fails
Not all refresh attempts succeed. Here's how to handle failures:
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
What to store:
{
"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_atto a time in the past - Example:
expires_at = current_time - 1000
expires_at to a time in the past
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_atis ~1 hour in the future
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
"invalid_token"
Test network errors:
- 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:
(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?
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
Right
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:
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
What to store
access_token
refresh_token (encrypted)
expires_at (current time + expires_in)
scope
Security rules
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