OAuth 2.0 Authorization Server
AuthStack provides a complete OAuth 2.0 Authorization Server implementation, allowing third-party applications to authenticate users through your platform using the Authorization Code flow with PKCE support.
Overview
Section titled “Overview”The OAuth 2.0 Authorization Code flow allows your applications to:
- Authenticate users securely without handling their passwords
- Support social login providers (Google, GitHub, Microsoft, Apple, Discord)
- Issue access and refresh tokens for API access
- Implement single sign-on (SSO) across multiple applications
Endpoints
Section titled “Endpoints”| Method | Endpoint | Description |
|---|---|---|
| GET | /oauth/authorize | Validate authorization request |
| POST | /oauth/authorize | Create authorization code (after login) |
| POST | /oauth/token | Exchange code for tokens |
Authorization Flow
Section titled “Authorization Flow”Step 1: Redirect to Authorization
Section titled “Step 1: Redirect to Authorization”Redirect users to the authorization endpoint with required parameters:
https://app.authstack.voostack.com/oauth/authorize? client_id=YOUR_CLIENT_ID& redirect_uri=https://yourapp.com/callback& response_type=code& scope=openid profile email& state=random_state_string& code_challenge=BASE64_URL_ENCODED_CHALLENGE& code_challenge_method=S256Parameters
Section titled “Parameters”| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Your application’s Client ID |
redirect_uri | Yes | Must match a registered redirect URI |
response_type | Yes | Must be code |
scope | No | Space-separated list of scopes |
state | Recommended | Random string for CSRF protection |
code_challenge | Recommended | PKCE code challenge |
code_challenge_method | No | S256 (default) or plain |
Step 2: User Authentication
Section titled “Step 2: User Authentication”The user will see a branded login page featuring:
- Your application’s logo (if configured)
- Custom colors matching your brand identity
- Email/password authentication
- Social login buttons (based on your app’s enabled providers)
- Application name and requested scopes
After successful authentication, the user is redirected to your redirect_uri:
https://yourapp.com/callback? code=AUTHORIZATION_CODE& state=random_state_stringStep 3: Exchange Code for Tokens
Section titled “Step 3: Exchange Code for Tokens”Exchange the authorization code for access and refresh tokens:
POST /oauth/tokenContent-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=https://yourapp.com/callback&client_id=YOUR_CLIENT_ID&code_verifier=ORIGINAL_CODE_VERIFIERFor confidential clients, include client_secret instead of code_verifier.
Token Response
Section titled “Token Response”{ "access_token": "eyJhbGciOiJSUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...", "scope": "openid profile email"}PKCE (Proof Key for Code Exchange)
Section titled “PKCE (Proof Key for Code Exchange)”PKCE is recommended for all clients and required for public clients (mobile/SPA).
Generate Code Verifier
Section titled “Generate Code Verifier”Create a random string (43-128 characters):
function generateCodeVerifier() { const array = new Uint8Array(32); crypto.getRandomValues(array); return base64UrlEncode(array);}Generate Code Challenge
Section titled “Generate Code Challenge”Hash the verifier with SHA-256:
async function generateCodeChallenge(verifier) { const encoder = new TextEncoder(); const data = encoder.encode(verifier); const hash = await crypto.subtle.digest('SHA-256', data); return base64UrlEncode(new Uint8Array(hash));}Refreshing Tokens
Section titled “Refreshing Tokens”Use the refresh token to get new access tokens:
POST /oauth/tokenContent-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN&client_id=YOUR_CLIENT_IDScopes
Section titled “Scopes”| Scope | Description |
|---|---|
openid | Access user’s unique identifier |
profile | Read user’s profile information |
email | Read user’s email address |
offline_access | Receive refresh tokens |
Complete Example (JavaScript)
Section titled “Complete Example (JavaScript)”// 1. Generate PKCE codesconst codeVerifier = generateCodeVerifier();const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store verifier for latersessionStorage.setItem('code_verifier', codeVerifier);
// 2. Generate state for CSRF protectionconst state = crypto.randomUUID();sessionStorage.setItem('oauth_state', state);
// 3. Redirect to authorizationconst authUrl = new URL('https://app.authstack.voostack.com/oauth/authorize');authUrl.searchParams.set('client_id', 'YOUR_CLIENT_ID');authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');authUrl.searchParams.set('response_type', 'code');authUrl.searchParams.set('scope', 'openid profile email');authUrl.searchParams.set('state', state);authUrl.searchParams.set('code_challenge', codeChallenge);authUrl.searchParams.set('code_challenge_method', 'S256');
window.location.href = authUrl.toString();Handling the Callback
Section titled “Handling the Callback”// On your callback pageconst params = new URLSearchParams(window.location.search);const code = params.get('code');const returnedState = params.get('state');
// Verify stateconst savedState = sessionStorage.getItem('oauth_state');if (returnedState !== savedState) { throw new Error('State mismatch - possible CSRF attack');}
// Exchange code for tokensconst codeVerifier = sessionStorage.getItem('code_verifier');
const response = await fetch('https://api.authstack.voostack.com/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'authorization_code', code: code, redirect_uri: 'https://yourapp.com/callback', client_id: 'YOUR_CLIENT_ID', code_verifier: codeVerifier, }),});
const tokens = await response.json();// Store tokens securelyComplete Example (Flutter)
Section titled “Complete Example (Flutter)”import 'dart:convert';import 'dart:math';import 'package:crypto/crypto.dart';import 'package:url_launcher/url_launcher.dart';
class AuthService { static const clientId = 'YOUR_CLIENT_ID'; static const redirectUri = 'myapp://callback';
String? _codeVerifier; String? _state;
String _generateRandomString(int length) { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; final random = Random.secure(); return List.generate(length, (_) => chars[random.nextInt(chars.length)]).join(); }
String _generateCodeChallenge(String verifier) { final bytes = utf8.encode(verifier); final digest = sha256.convert(bytes); return base64UrlEncode(digest.bytes).replaceAll('=', ''); }
Future<void> startAuth() async { _codeVerifier = _generateRandomString(64); _state = _generateRandomString(32); final codeChallenge = _generateCodeChallenge(_codeVerifier!);
final uri = Uri.parse('https://app.authstack.voostack.com/oauth/authorize').replace( queryParameters: { 'client_id': clientId, 'redirect_uri': redirectUri, 'response_type': 'code', 'scope': 'openid profile email', 'state': _state, 'code_challenge': codeChallenge, 'code_challenge_method': 'S256', }, );
await launchUrl(uri, mode: LaunchMode.externalApplication); }
Future<Map<String, dynamic>> handleCallback(Uri uri) async { final code = uri.queryParameters['code']; final returnedState = uri.queryParameters['state'];
if (returnedState != _state) { throw Exception('State mismatch'); }
final response = await http.post( Uri.parse('https://api.authstack.voostack.com/oauth/token'), headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: { 'grant_type': 'authorization_code', 'code': code, 'redirect_uri': redirectUri, 'client_id': clientId, 'code_verifier': _codeVerifier, }, );
return jsonDecode(response.body); }}Error Responses
Section titled “Error Responses”Authorization Errors
Section titled “Authorization Errors”If validation fails, the user sees an error page. For recoverable errors, the user is redirected:
https://yourapp.com/callback? error=access_denied& error_description=User denied access& state=random_state_stringToken Errors
Section titled “Token Errors”{ "error": "invalid_grant", "error_description": "Authorization code expired"}| Error | Description |
|---|---|
invalid_request | Missing required parameter |
invalid_client | Unknown client_id or incorrect client_secret |
invalid_grant | Invalid, expired, or already used code |
invalid_scope | Requested scope not allowed |
unsupported_grant_type | Grant type not supported |
unsupported_response_type | Response type not supported |
Security Best Practices
Section titled “Security Best Practices”- Always use PKCE - Even for confidential clients
- Validate state - Prevent CSRF attacks
- Use HTTPS - All redirect URIs must use HTTPS (except localhost)
- Short-lived codes - Authorization codes expire in 10 minutes
- Single-use codes - Codes can only be exchanged once
- Validate redirect URIs - Only registered URIs are allowed
- Rotate secrets - Periodically regenerate client secrets
Configuring Your Application
Section titled “Configuring Your Application”Before using OAuth, configure your application in the AuthStack dashboard:
- Go to Applications > Your App
- Add your Redirect URIs
- Enable desired OAuth Providers (Google, GitHub, etc.)
- Configure Allowed Scopes
- Set up Custom Branding (logo, primary/secondary colors)
- Note your Client ID and Client Secret
Branding Your OAuth Page
Section titled “Branding Your OAuth Page”Make the authentication experience feel native to your application:
{ "logoUrl": "https://yourapp.com/logo.png", "primaryColor": "#3B82F6", "secondaryColor": "#10B981"}See Custom Branding for full details.
Next Steps
Section titled “Next Steps”- Applications API - Manage OAuth applications
- Webhooks - Receive user authentication events
- Getting Started - Quick start guide