Skip to content

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.

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
MethodEndpointDescription
GET/oauth/authorizeValidate authorization request
POST/oauth/authorizeCreate authorization code (after login)
POST/oauth/tokenExchange code for tokens

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=S256
ParameterRequiredDescription
client_idYesYour application’s Client ID
redirect_uriYesMust match a registered redirect URI
response_typeYesMust be code
scopeNoSpace-separated list of scopes
stateRecommendedRandom string for CSRF protection
code_challengeRecommendedPKCE code challenge
code_challenge_methodNoS256 (default) or plain

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_string

Exchange the authorization code for access and refresh tokens:

POST /oauth/token
Content-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_VERIFIER

For confidential clients, include client_secret instead of code_verifier.

{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...",
"scope": "openid profile email"
}

PKCE is recommended for all clients and required for public clients (mobile/SPA).

Create a random string (43-128 characters):

function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64UrlEncode(array);
}

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));
}

Use the refresh token to get new access tokens:

POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&
refresh_token=YOUR_REFRESH_TOKEN&
client_id=YOUR_CLIENT_ID
ScopeDescription
openidAccess user’s unique identifier
profileRead user’s profile information
emailRead user’s email address
offline_accessReceive refresh tokens
// 1. Generate PKCE codes
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store verifier for later
sessionStorage.setItem('code_verifier', codeVerifier);
// 2. Generate state for CSRF protection
const state = crypto.randomUUID();
sessionStorage.setItem('oauth_state', state);
// 3. Redirect to authorization
const 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();
// On your callback page
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const returnedState = params.get('state');
// Verify state
const savedState = sessionStorage.getItem('oauth_state');
if (returnedState !== savedState) {
throw new Error('State mismatch - possible CSRF attack');
}
// Exchange code for tokens
const 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 securely
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);
}
}

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_string
{
"error": "invalid_grant",
"error_description": "Authorization code expired"
}
ErrorDescription
invalid_requestMissing required parameter
invalid_clientUnknown client_id or incorrect client_secret
invalid_grantInvalid, expired, or already used code
invalid_scopeRequested scope not allowed
unsupported_grant_typeGrant type not supported
unsupported_response_typeResponse type not supported
  1. Always use PKCE - Even for confidential clients
  2. Validate state - Prevent CSRF attacks
  3. Use HTTPS - All redirect URIs must use HTTPS (except localhost)
  4. Short-lived codes - Authorization codes expire in 10 minutes
  5. Single-use codes - Codes can only be exchanged once
  6. Validate redirect URIs - Only registered URIs are allowed
  7. Rotate secrets - Periodically regenerate client secrets

Before using OAuth, configure your application in the AuthStack dashboard:

  1. Go to Applications > Your App
  2. Add your Redirect URIs
  3. Enable desired OAuth Providers (Google, GitHub, etc.)
  4. Configure Allowed Scopes
  5. Set up Custom Branding (logo, primary/secondary colors)
  6. Note your Client ID and Client Secret

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.