agentleFS
MindrallyMindrally/skills/oauth-implementation/SKILL.md

oauth-implementation

Skill275 starsChanged 35 days agoApache-2.0View on GitHub

Guidelines for implementing OAuth 2.0 and OAuth 2.1 authentication flows with security best practices and PKCE

  • Sends data out

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 4 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Always use OAuth 2.1 patterns (PKCE required, no implicit flow)

    line 15
  2. Distinctive:

    Use HTTPS for all OAuth communications

    line 16
  3. Distinctive:

    Follow the principle of least privilege for scopes

    line 18
  4. Distinctive:

    Always use PKCE - the code_verifier ensures only the original requester can exchange the code.

    line 289

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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

// 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

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

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

// 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

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

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

// 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

// 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

// Always use and validate the state parameter
const state = crypto.randomBytes(32).toString('hex');
// Store in session and validate on callback

3. Open Redirect

// 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

// 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

// 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

// Store tokens encrypted in session or database
const encryptedToken = encrypt(accessToken, SESSION_ENCRYPTION_KEY);
req.session.encryptedAccessToken = encryptedToken;

Error Handling

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

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.