OAuth2
What is OAuth2?
OAuth 2.0 is the industry-standard protocol for authorization. It allows your app to access specific data in user accounts with explicit user consent, without requiring users to share their passwords.
- Secure Access: Users authorize without sharing passwords
- Granular Permissions: Request only the scopes your app needs
- Token-Based: Access and refresh tokens for secure API calls
- User Consent: Explicit approval for each permission
How OAuth2 Works
OAuth2 uses the authorization code grant flow:
The flow ensures secure, controlled access by:
- Requiring explicit user consent
- Using temporary authorization codes
- Providing time-limited access tokens
- Supporting granular permissions through scopes
Creating an OAuth2 App
Select app type
Choose the appropriate app type:
Organization App
Personal App
Organization App: Generate organization-level access tokens
Use for:
- Contract data reading
- Timesheets management
- Invoice adjustments
- SCIM API integration
- Accounting data access
Provides access to all organization resources based on granted scopes.
OAuth2 Flow Implementation
Step 1: Request Permissions
Redirect users to Deel’s authorization endpoint with the required parameters:
Required Parameters:
Example URL:
Step 2: User Authorizes
The user sees a consent screen showing:
- Your app name and logo
- What permissions (scopes) you’re requesting
- Option to approve or deny
When they approve, they’re redirected to your redirect_uri with an authorization code:
Always verify the state parameter matches what you sent to prevent CSRF attacks.
Step 3: Exchange Code for Access Token
Exchange the authorization code for an access token by making a POST request:
Authentication:
- Use HTTP Basic authentication
- Encode your credentials:
base64(client_id:client_secret)
Example encoding:
Response:
- Access tokens are valid for 30 days (2,592,000 seconds)
- Refresh tokens are valid for 90 days
Step 4: Make Authenticated Requests
Use the access token to make API requests. OAuth2 requests require two headers:
OAuth2 requests require both the Authorization header AND the x-client-id header. Missing either will result in a 401 error.
Token Rotation
Access tokens expire after 30 days. Refresh them before expiration to maintain uninterrupted access.
Refreshing Access Tokens
Make a POST request with your refresh token:
Response includes:
- New access token (valid for another 30 days)
- Token expiration time (2,592,000 seconds)
- New refresh token (use this for next refresh)
- Token type (Bearer)
- Authorized scopes
Refresh tokens are single-use. Each successful token refresh invalidates the previous refresh token and returns a new one. If you attempt to reuse a refresh token that has already been exchanged, the server returns an invalid_grant error. Reuse of a refresh token may indicate token compromise, and the authorization server may revoke all tokens associated with that grant as a security measure.
Proactive rotation: Do not wait until tokens expire. Set up a scheduled job to refresh tokens every 25 days to avoid disruption. Always store the new refresh token returned in the response, as the previous one is no longer valid.
Best Practices for Token Rotation
Automatic refresh
Implement automatic token refresh in your application:
- Check token expiration before each request
- Refresh proactively (5 days before expiration)
- Handle refresh failures gracefully
- Always store and use the newly returned refresh token after each refresh, as the previous one is immediately invalidated
- Store new tokens securely
Handle expired tokens
If a request fails with 401:
- Attempt to refresh the token
- Retry the original request with the new token
- If the refresh fails with
invalid_grant, the refresh token may have already been used (not just expired). Prompt the user to re-authorize
Single-use refresh tokens
Deel implements refresh token rotation as defined in RFC 9700 Section 4.14.2:
- Each refresh token can only be exchanged once for a new access token and refresh token pair
- After a successful exchange, the previous refresh token becomes inactive immediately
- If a refresh token is used more than once, the authorization server treats this as a potential compromise signal
- The server may revoke all tokens associated with the grant to protect the account
- Always replace your stored refresh token with the new one returned in the refresh response
Secure token storage
- Store tokens encrypted at rest
- Never expose tokens in URLs or logs
- Use secure session storage for web apps
- Implement token revocation on logout
Scopes Reference
Request only the scopes your application needs. Each API endpoint specifies which scopes are required - check the API Reference for details.
Scope patterns: Scopes follow the pattern {resource}:read or {resource}:write (e.g., contracts:read, timesheets:write).
Common scope combinations:
Recommended App Types by Use Case
Troubleshooting
401 Unauthorized Error
Common causes:
- Invalid or expired access token
- Missing
x-client-idheader - Token doesn’t have required scopes
Solutions:
- Refresh the access token
- Verify both
Authorizationandx-client-idheaders are present - Check token scopes match endpoint requirements
403 Forbidden Error
Common causes:
- Token lacks required scopes
- User doesn’t have permission to access resource
- App type mismatch (personal app trying to access org resources)
Solutions:
- Review requested scopes during authorization
- Use organization app for org-wide resources
- Verify user has appropriate permissions
Invalid Grant Error
When exchanging code or refreshing token:
- Authorization code already used
- Refresh token expired
- Refresh token already used (refresh tokens are single-use)
redirect_uridoes not match
Solutions:
- Authorization codes are single-use only
- Request new authorization if the refresh token has expired
- Store the new refresh token from each refresh response and discard the old one. If you receive this error unexpectedly, it may indicate token compromise — re-authorize the user
- Ensure
redirect_uriexactly matches the registered URI
State Mismatch
Cause:
- CSRF protection - returned
statedoesn’t match sent state
Solutions:
- Store state value in session before redirect
- Validate returned state matches stored value
- Generate new state for each authorization request
Security Best Practices
Validate State Parameter
Always verify the state parameter to prevent CSRF attacks
Secure Client Secret
Never expose client secret in client-side code or version control
Use HTTPS
Redirect URIs must use HTTPS (except localhost for development)
Minimal Scopes
Request only the permissions your app actually needs
Token Encryption
Store access and refresh tokens encrypted at rest
Implement Logout
Revoke tokens when users disconnect or log out