Implement OAuth 2.0 to let DoorFlow users authorize your application. This guide covers the complete flow from testing to production.
Overview
DoorFlow uses the OAuth 2.0 authorization code flow. Your application will:
- Create an OAuth application in the developer portal
- Get
client_idandclient_secret - Implement the authorization code flow
- Test with a DoorFlow sandbox account
- Request approval for production
- Connect to real customer accounts
Time to implement: 30-60 minutes for a basic integration.
Application States
Your OAuth application has different states:
Testing (enabled: true, approved: false)
- Development state
- Connected to DoorFlow sandbox account
- Test with fake doors and events
- Full OAuth flow works
- Not visible to customers
Approved (enabled: true, approved: true)
- Production ready
- Can connect to real customer accounts
- Listed in DoorFlow marketplace (optional)
- Full OAuth flow works with any DoorFlow account
Disabled/Suspended
- Application not active
- Cannot authorize new accounts
Step 1: Create OAuth Application
- Go to DoorFlow Developer Portal
- Sign in or create developer account
- Click "Create New Application"
- Fill out form:
Required fields
- Development: Can use
http://localhost:3000/callback - Production: Must be HTTPS
https://yourapp.com/callback - Used for both testing and approved states
http://localhost:3000/callback
https://yourapp.com/callback
-
account.person- Manage people and credentials -
account.channel.readonly- View doors -
account.event.access.readonly- View access events - See [Choosing the Right Scopes] for full list
account.person - Manage people and credentials
account.channel.readonly - View doors
account.event.access.readonly - View access events
- Click "Create Application"
What you get
client_id - Public identifier (safe to embed in frontend)
client_secret - Secret key (NEVER expose in frontend)
Step 2: Testing State
Your application is now in testing state. This means:
You can
client_id and client_secret immediately
You cannot
Sandbox account features
Step 3: Implement OAuth Flow
The OAuth authorization code flow has 5 steps:
1. User clicks "Connect to DoorFlow"
↓
2. Redirect to DoorFlow authorization URL
↓
3. User logs in and grants permission
↓
4. DoorFlow redirects back with authorization code
↓
5. Exchange code for access token (server-side)
3.1: Build Authorization URL
When user clicks "Connect to DoorFlow" button:
function connectToDoorFlow() {
const params = new URLSearchParams({
response_type: 'code',
client_id: 'YOUR_CLIENT_ID',
redirect_uri: 'YOUR_REDIRECT_URI',
scope: 'account.person account.event.access.readonly',
state: generateRandomString(32) // CSRF protection
});
// Store state for verification
sessionStorage.setItem('oauth_state', params.get('state'));
// Redirect user to DoorFlow
window.location.href = `https://api.doorflow.com/oauth/authorize?${params}`;
}
function generateRandomString(length) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
Parameters
response_type - Always "code"
client_id - Your application's client ID
redirect_uri - Must match registered URI exactly
scope - Space-separated list of scopes
state - Random string for CSRF protection (recommended)
3.2: User Authorizes
DoorFlow shows authorization screen:
- Your app name and description
- Which scopes you're requesting
- Which DoorFlow account to connect
Testing state: User can only connect the sandbox account
Approved state: User can connect any DoorFlow account they have access to
User clicks "Authorize" or "Deny"
3.3: Handle Callback
DoorFlow redirects back to your redirect_uri:
If approved:
https://yourapp.com/callback?code=AUTH_CODE_HERE&state=SAME_STATE
If denied:
https://yourapp.com/callback?error=access_denied&state=SAME_STATE
Your callback handler:
// Node.js/Express example
app.get('/callback', async (req, res) => {
const { code, state, error } = req.query;
// 1. Verify state (CSRF protection)
if (state !== req.session.oauth_state) {
return res.status(403).send('Invalid state - possible CSRF attack');
}
// 2. Check for errors
if (error) {
console.log('User denied authorization:', error);
return res.redirect('/error?message=Authorization denied');
}
// 3. Exchange code for token
try {
const tokens = await exchangeCodeForToken(code);
// 4. Store tokens securely
req.session.access_token = tokens.access_token;
req.session.refresh_token = encrypt(tokens.refresh_token); // Encrypt!
// 5. Redirect to app
res.redirect('/dashboard');
} catch (error) {
console.error('Token exchange failed:', error);
res.redirect('/error?message=Authentication failed');
}
});
3.4: Exchange Code for Token
CRITICAL: This must happen server-side only. Never expose client_secret in frontend.
async function exchangeCodeForToken(code) {
const response = await fetch('https://api.doorflow.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: process.env.REDIRECT_URI,
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET // Server-side only!
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Token exchange failed: ${error.error_description}`);
}
return await response.json();
}
Response:
{
"access_token": "eyJhbGci...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "a1b2c3...",
"scope": "account.person account.event.access.readonly",
"created_at": 1704067200
}
Fields
access_token - Use for API calls (1 hour lifetime)
refresh_token - Use to get new access tokens
expires_in - Seconds until token expires (3600 = 1 hour)
scope - Scopes that were actually granted
3.5: Use Access Token
Include in Authorization header for all API requests:
async function callDoorFlowAPI(endpoint, options = {}) {
const response = await fetch(`https://api.doorflow.com${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
...options.headers
}
});
return await response.json();
}
// Example: Get account info
const account = await callDoorFlowAPI('/api/3/account');
console.log('Connected to:', account.name);
Step 4: Test with Sandbox Account
Your testing state application connects to a DoorFlow sandbox account.
Testing checklist
Test authorization flow:
- Click "Connect to DoorFlow" in your app
- Authorize with sandbox account
- Verify redirect back to your app
- Confirm tokens stored correctly
Test API calls:
- Create a person in sandbox
- Issue a credential
- Query channels
- View events
Test token refresh:
- Wait for token to expire (1 hour) or manually expire
- Refresh token using refresh_token
- Verify new tokens work
Test error handling:
- What if user denies authorization?
- What if token expires?
- What if API returns errors?
Sandbox account features
Step 5: Request Production Approval
When testing is complete and you're ready for production:
- Test thoroughly - Make sure your integration works
-
Review security
•Client secret never exposed in frontend•Refresh tokens encrypted in database•Using HTTPS redirect URI•State parameter validated -
In Developer Portal
•Click "Request Approval" on your application•Fill out production readiness form•DoorFlow team reviews your application -
DoorFlow reviews
•Checks OAuth implementation•Verifies security practices•Tests your integration•May request changes -
Approval granted
•Application state changes to "Approved"•Can now connect to any DoorFlow customer account•Optional: Listed in DoorFlow marketplace
Timeline: Approval typically takes 3-5 business days.
Step 6: Production Use
Once approved, your application can connect to real customer accounts.
What changes
What stays the same
client_id and client_secret
Production best practices
Monitor authorization rates:
- How many users authorize?
- How many deny?
- Track conversion rate
Handle token refresh:
- Automatic refresh on 401
- Don't interrupt user experience
- See [OAuth Token Refresh] guide
Monitor API usage:
- Stay under rate limits (120 req/minute)
- Log all API errors
- Set up alerts
Support customers:
- Clear error messages
- Help docs for users
- Support email in your app
Token Storage
Access token
Refresh token
Example storage:
// Store tokens (encrypt refresh token!)
async function saveTokens(userId, tokens) {
await db.query(
'INSERT INTO oauth_tokens (user_id, access_token, refresh_token, expires_at) VALUES (?, ?, ?, ?)',
[
userId,
tokens.access_token,
encrypt(tokens.refresh_token), // Encrypt!
Date.now() + (tokens.expires_in * 1000)
]
);
}
// Retrieve tokens
async function getTokens(userId) {
const row = await db.query(
'SELECT access_token, refresh_token, expires_at FROM oauth_tokens WHERE user_id = ?',
[userId]
);
return {
access_token: row.access_token,
refresh_token: decrypt(row.refresh_token),
expires_at: row.expires_at
};
}
Common Errors
"redirect_uri_mismatch"
- URI doesn't match registered value exactly
- Check trailing slashes, http vs https
- URLs are case-sensitive
"invalid_client"
- Client ID or secret is wrong
- Check credentials from developer portal
"invalid_grant"
- Authorization code expired (10 minute lifetime)
- Code already used (one-time use only)
- Redirect URI doesn't match
"invalid_scope"
- Requesting scope your app doesn't have
- Update scopes in developer portal
"access_denied"
- User clicked "Deny"
- Handle gracefully in your app
Security Checklist
Before going live:
- Client secret stored server-side only (never in frontend)
- Using HTTPS for redirect URI in production
- State parameter validated (CSRF protection)
- Refresh tokens encrypted in database
- Tokens never logged
- Error handling implemented
- Token refresh working
- Tested thoroughly in sandbox
Complete Example
Full OAuth implementation:
// server.js (Node.js/Express)
const express = require('express');
const session = require('express-session');
const crypto = require('crypto');
const app = express();
app.use(session({
secret: 'your-session-secret',
resave: false,
saveUninitialized: false,
cookie: { secure: true, httpOnly: true }
}));
// Connect to DoorFlow button
app.get('/connect', (req, res) => {
const state = crypto.randomBytes(32).toString('hex');
req.session.oauth_state = state;
const params = new URLSearchParams({
response_type: 'code',
client_id: process.env.DOORFLOW_CLIENT_ID,
redirect_uri: process.env.DOORFLOW_REDIRECT_URI,
scope: 'account.person account.event.access.readonly',
state: state
});
res.redirect(`https://api.doorflow.com/oauth/authorize?${params}`);
});
// OAuth callback
app.get('/callback', async (req, res) => {
const { code, state, error } = req.query;
// Verify state
if (state !== req.session.oauth_state) {
return res.status(403).send('Invalid state');
}
// Handle denial
if (error) {
return res.redirect('/error?message=Authorization denied');
}
try {
// Exchange code for token
const tokenResponse = await fetch('https://api.doorflow.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: process.env.DOORFLOW_REDIRECT_URI,
client_id: process.env.DOORFLOW_CLIENT_ID,
client_secret: process.env.DOORFLOW_CLIENT_SECRET
})
});
if (!tokenResponse.ok) {
throw new Error('Token exchange failed');
}
const tokens = await tokenResponse.json();
// Store tokens securely
req.session.access_token = tokens.access_token;
// In production: encrypt and store refresh_token in database
// Get account info
const accountResponse = await fetch('https://api.doorflow.com/api/3/account', {
headers: { 'Authorization': `Bearer ${tokens.access_token}` }
});
const account = await accountResponse.json();
res.redirect(`/dashboard?connected=${account.name}`);
} catch (error) {
console.error('OAuth error:', error);
res.redirect('/error?message=Authentication failed');
}
});
app.listen(3000);
Next Steps
Token expired?
- [OAuth Token Refresh] - Automatic token refresh
Building mobile app?
- [OAuth PKCE] - Secure mobile OAuth without client secret
Need help with scopes?
- [Choosing the Right Scopes] - Scope selection guide
Ready to code?
- [Common Workflows] - Complete examples using OAuth