oauth-implementation · mindrally — Claude Code skill: what it does, how to install
Guides coding agents through OAuth 2.0 and 2.1 setup with PKCE, state checks, and token rotation. Built to prevent insecure grants like implicit flow.
npx skills add Mindrally/skills --skill oauth-implementation -a claude-codeWhat oauth-implementation does
This skill guides coding assistants to implement OAuth 2.0 and OAuth 2.1 flows following strict security standards. It drops deprecated patterns such as Implicit grant and Resource Owner Password Credentials. Authorization code flow with PKCE is required across all clients. The agent generates a cryptographic code_verifier and SHA-256 code_challenge directly in the flow.
The guidelines cover browser authentication and server-side token exchanges. They enforce exact-string matching on redirect URIs to block bypasses. Wildcards are not permitted. The workflow validates the state parameter to prevent CSRF attacks and handles refresh token rotation.
Server-side JWT validation in the provided implementation requires the jsonwebtoken and jwks-rsa npm packages.
What it helps with
- generate PKCE code verifier and challenge parameters
- implement confidential client token exchange on Express
- validate JWT access tokens against a JWKS endpoint
- configure refresh token rotation to update sessions
Example prompts for the agent
- Write an OAuth 2.1 authorization request with PKCE parameters for the browser
- Implement an Express
/oauth/callbackroute that exchanges authorization codes for tokens - Validate incoming JWT access tokens against a JWKS endpoint using RS256
How to install oauth-implementation
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill oauth-implementation -a claude-code2Manually: put the files in place
~/.claude/skills/oauth-implementation/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/oauth-implementation/
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill oauth-implementation -a codex2Manually: put the files in place
.agents/skills/oauth-implementation/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill oauth-implementation -a cursor2Manually: put the files in place
.cursor/skills/oauth-implementation/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install Mindrally/skills oauth-implementation2Manually: put the files in place
.github/skills/oauth-implementation/SKILL.md1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill oauth-implementation -a gemini-cli2Platform CLI
gemini skills install https://github.com/Mindrally/skills --path oauth-implementationManually: put the files in place
.devin/skills/oauth-implementation/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add Mindrally/skills --skill oauth-implementation -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about oauth-implementation
How to install oauth-implementation in Claude Code?
Run npx skills add Mindrally/skills --skill oauth-implementation -a claude-code in your terminal. You can also save the file directly to ~/.claude/skills/oauth-implementation/SKILL.md.
Which grant types are deprecated by this skill?
Following OAuth 2.1 rules, the skill removes Implicit grant and Resource Owner Password Credentials. It enforces Authorization Code Flow with PKCE for every client.
What dependencies are needed for token validation?
The server-side token verification examples rely on jsonwebtoken and jwks-rsa to verify signatures against the JWKS endpoint.
Source text
SKILL.md · Apache-2.0
---
name: oauth-implementation
description: Guidelines for implementing OAuth 2.0 and OAuth 2.1 authentication flows with security best practices and PKCE
metadata:
maintainer: Mindrally
source: https://github.com/Mindrally/skills
---
# 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 gracefully
© Mindrally · Apache-2.0 · Mindrally/skills
Similar skills
All skills for Security →oauth-implementation by other authors comes first.