oauth-implementation
Guidelines for implementing OAuth 2.0 and OAuth 2.1 authentication flows with security best practices and PKCE
Works with
---
name: oauth-implementation
description: Guidelines for implementing OAuth 2.0 and OAuth 2.1 authentication flows with security best practices and PKCE
license: Apache-2.0
---
# OAuth Implementation
You are an expert in OAuth 2.0 and OAuth 2.1 implementation. Follow these guidelines when implementing OAuth authentication flows.
## Core Principles
- Always use OAuth 2.1 patterns (PKCE required, no implicit flow)
- Use HTTPS for all OAuth communications
- Implement proper state management for CSRF protection
- Follow the principle of least privilege for scopes
- Validate all tokens server-side
## OAuth 2.1 Key Requirements
OAuth 2.1 consolidates best practices and deprecates insecure patterns:
- PKCE is required for ALL clients using authorization code flow
- Implicit grant is removed
- Resource Owner Password Credentials grant is removed
- Redirect URIs must use exact string matching
- Refresh tokens must be sender-constrained or use rotation
## Authorization Code Flow with PKCE
### Step 1: Generate PKCE Parameters
```javascript
// Generate cryptographically secure code verifier
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64URLEncode(array);
}
// Create code challenge from verifier
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const digest = await crypto.subtle.digest('SHA-256', data);
return base64URLEncode(new Uint8Array(digest));
}
function base64URLEncode(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
```
### Step 2: Authorization Request
```javascript
async function initiateOAuthFlow() {
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
const state = generateSecureRandomString();
// Store for later verification
sessionStorage.setItem('oauth_code_verifier', codeVerifier);
sessionStorage.setItem('oauth_state', state);
const params = new URLSearchParams({
response_type: 'code',
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: 'openid profile email',
state: state,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
});
window.location.href = `${AUTHORIZATION_ENDPOINT}?${params}`;
}
```
### Step 3: Handle Callback and Token Exchange
```javascript
async function handleCallback() {
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
const error = params.get('error');
// Check for errors
if (error) {
throw new Error(`OAuth error: ${error} - ${params.get('error_description')}`);
}
// Validate state to prevent CSRF
const storedState = sessionStorage.getItem('oauth_state');
if (state !== storedState) {
throw new Error('Invalid state parameter - possible CSRF attack');
}
// Retrieve code verifier
const codeVerifier = sessionStorage.getItem('oauth_code_verifier');
// Exchange code for tokens
const response = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
code_verifier: codeVerifier,
}),
});
if (!response.ok) {
throw new Error('Token exchange failed');
}
const tokens = await response.json();
// Clean up
sessionStorage.removeItem('oauth_code_verifier');
sessionStorage.removeItem('oauth_state');
return tokens;
}
```
## Server-Side Implementation
### Confidential Client Token Exchange
```javascript
// Node.js/Express example
app.post('/oauth/callback', async (req, res) => {
const { code, state } = req.body;
// Validate state
if (state !== req.session.oauthState) {
return res.status(400).json({ error: 'Invalid state' });
}
try {
const tokenResponse = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
// Client authentication for confidential clients
Authorization: `Basic ${Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')}`,
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: REDIRECT_URI,
code_verifier: req.session.codeVerifier,
}),
});
const tokens = await tokenResponse.json();
// Store tokens securely server-side
req.session.accessToken = tokens.access_token;
req.session.refreshToken = tokens.refresh_token;
res.redirect('/dashboard');
} catch (error) {
res.status(500).json({ error: 'Token exchange failed' });
}
});
```
## Token Security Best Practices
### Access Token Validation
```javascript
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const client = jwksClient({
jwksUri: `${ISSUER}/.well-known/jwks.json`,
cache: true,
cacheMaxAge: 600000, // 10 minutes
});
function getSigningKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
if (err) return callback(err);
const signingKey = key.getPublicKey();
callback(null, signingKey);
});
}
async function validateToken(token) {
return new Promise((resolve, reject) => {
jwt.verify(
token,
getSigningKey,
{
audience: EXPECTED_AUDIENCE,
issuer: EXPECTED_ISSUER,
algorithms: ['RS256'], // Whitelist allowed algorithms
},
(err, decoded) => {
if (err) reject(err);
else resolve(decoded);
}
);
});
}
```
### Refresh Token Rotation
```javascript
async function refreshAccessToken(refreshToken) {
const response = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: CLIENT_ID,
}),
});
if (!response.ok) {
// Refresh token may be expired or revoked
throw new Error('Refresh token invalid');
}
const tokens = await response.json();
// If rotation is enabled, you'll receive a new refresh token
// Store the new refresh token and invalidate the old one
return tokens;
}
```
## Security Requirements
### Redirect URI Validation
```javascript
// Server-side: validate redirect URIs against whitelist
const ALLOWED_REDIRECT_URIS = [
'https://myapp.com/callback',
'https://myapp.com/oauth/callback',
];
function validateRedirectUri(uri) {
// Exact string matching - no wildcards
return ALLOWED_REDIRECT_URIS.includes(uri);
}
```
### Scope Management
```javascript
// Request minimum necessary scopes
const SCOPES = {
basic: 'openid profile email',
readOnly: 'openid profile email read:data',
fullAccess: 'openid profile email read:data write:data',
};
// Validate scopes on the server
function validateScopes(requestedScopes, allowedScopes) {
const requested = requestedScopes.split(' ');
const allowed = allowedScopes.split(' ');
return requested.every(scope => allowed.includes(scope));
}
```
## Common Vulnerabilities to Prevent
### 1. Authorization Code Injection
Always use PKCE - the code_verifier ensures only the original requester can exchange the code.
### 2. CSRF Attacks
```javascript
// Always use and validate the state parameter
const state = crypto.randomBytes(32).toString('hex');
// Store in session and validate on callback
```
### 3. Open Redirect
```javascript
// Never construct redirect URIs from user input
// Always use whitelisted URIs
const redirectUri = ALLOWED_REDIRECT_URIS[0]; // Don't: req.query.redirect_uri
```
### 4. Token Leakage
```javascript
// Never log tokens
console.log('User authenticated'); // Good
console.log(`Token: ${accessToken}`); // NEVER DO THIS
// Don't include tokens in URLs
// Use Authorization header instead
fetch('/api/resource', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
```
## Token Storage Recommendations
### Browser Applications
```javascript
// Option 1: Memory (most secure, but lost on refresh)
let accessToken = null;
// Option 2: HttpOnly cookies (requires backend)
// Set by server with appropriate flags
// Secure, HttpOnly, SameSite=Strict
// Option 3: sessionStorage (cleared when tab closes)
sessionStorage.setItem('access_token', token);
// Avoid localStorage for sensitive tokens
// Vulnerable to XSS attacks
```
### Server Applications
```javascript
// Store tokens encrypted in session or database
const encryptedToken = encrypt(accessToken, SESSION_ENCRYPTION_KEY);
req.session.encryptedAccessToken = encryptedToken;
```
## Error Handling
```javascript
const OAUTH_ERRORS = {
invalid_request: 'The request is missing a required parameter',
unauthorized_client: 'The client is not authorized',
access_denied: 'The user denied the request',
unsupported_response_type: 'The response type is not supported',
invalid_scope: 'The requested scope is invalid',
server_error: 'The authorization server encountered an error',
temporarily_unavailable: 'The server is temporarily unavailable',
};
function handleOAuthError(error, errorDescription) {
const message = OAUTH_ERRORS[error] || 'Unknown error';
console.error(`OAuth Error: ${message}. Details: ${errorDescription}`);
// Show user-friendly error message
}
```
## Testing Checklist
- [ ] PKCE flow works correctly
- [ ] State parameter is validated
- [ ] Invalid state is rejected
- [ ] Token expiration is handled
- [ ] Refresh token rotation works
- [ ] Invalid tokens are rejected
- [ ] Scopes are properly enforced
- [ ] Redirect URIs are validated
- [ ] Error cases are handled gracefullyMore Security skills
azure-cost
microsoft/azure-skills
Azure cost management: query costs, forecast spending, optimize to reduce waste. WHEN: \"Azure costs\", \"Azure bill\", \"cost breakdown\", \"how much am I spending\", \"forecast spending\", \"optimize costs\", \"reduce spending\", \"orphaned resources\", \"rightsize VMs\", \"cost spike\", \"reduce storage costs\", \"AKS cost\". DO NOT USE FOR: deploying resources, provisioning, diagnostics, or security audits.
entra-app-registration
microsoft/azure-skills
Guides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.
azure-messaging
microsoft/azure-skills
Troubleshoot and resolve issues with Azure Messaging SDKs for Event Hubs and Service Bus. Covers connection failures, authentication errors, message processing issues, and SDK configuration problems. WHEN: event hub SDK error, service bus SDK issue, messaging connection failure, AMQP error, event processor host issue, message lock lost, message lock expired, lock renewal, lock renewal batch, send timeout, receiver disconnected, SDK troubleshooting, azure messaging SDK, event hub consumer, service bus queue issue, topic subscription error, enable logging event hub, service bus logging, eventhub python, servicebus java, eventhub javascript, servicebus dotnet, event hub checkpoint, event hub not receiving messages, service bus dead letter, batch processing lock, session lock expired, idle timeout, connection inactive, link detach, slow reconnect, session error, duplicate events, offset reset, receive batch.

