Skip to main content
oauth-flow OAuth 2.0 and OIDC integration with PKCE, Supabase Auth providers, and redirect URI validation
Ir a la instalación Skills Marketplace Descubre y explora habilidades de IA creadas por la comunidad.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Copiar promptMostrar detalles del prompt Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/CleanExpo/Unite-Group --skill oauth-flowEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Descargar Zip Descargando... Más de este repositorio The authoritative environment-variable registry for the Unite-Group Nexus. Use whenever adding, reading, renaming, or debugging an env var or secret — new integration config, a `process.env.X` read, an auth/OAuth wiring, a cron secret, a "which variable holds this" question, or a "works locally but not in prod" symptom. Also use before pinning any variable name in code, and when reconciling the two credential planes (Vercel prod vs the local hermes fleet). Prevents the `APIFY_API_KEY` vs `APIFY_API_TOKEN` class of drift.
Create, verify, and promote Supabase schema changes via database branches. Every schema change/migration must be validated on a Supabase database branch before prod (see CLAUDE.md line 44).
Verify production Supabase schema before shipping code that touches it. Use BEFORE writing or reviewing ANY code that reads or writes a Supabase table in the Nexus — new queries, inserts, updates, CHECK-constrained values, RLS-dependent reads, cron data access — even one-line changes and even when the table "obviously" exists. Also use immediately when a query fails with "column does not exist", when inserts appear to succeed but produce no rows, or when generated TypeScript types disagree with runtime behaviour.
SOC
Basado en la clasificación ocupacional SOC
name oauth-flow type skill version 1.0.0 priority 2 domain security description OAuth 2.0 and OIDC integration with PKCE, Supabase Auth providers, and redirect URI validation
OAuth Flow
OAuth 2.0 and OIDC integration patterns with PKCE, provider configuration, and session management for NodeJS-Starter-V1.
Metadata
Field Value Skill ID oauth-flowCategory Authentication & Security Complexity High Complements api-client, rbac-patterns,
secret-management
Description Codifies OAuth 2.0 and OpenID Connect patterns for NodeJS-Starter-V1: authorisation code flow with PKCE, provider configuration for Google and GitHub, Supabase Auth integration, session management, token refresh, account linking, and security best practices for redirect URI validation.
When to Apply
Positive Triggers
Adding social login (Google, GitHub) to the application
Implementing OAuth 2.0 authorisation code flow with PKCE
Configuring Supabase Auth with external providers
Managing OAuth tokens, refresh, and session lifecycle
Linking multiple OAuth providers to a single user account
Negative Triggers
JWT creation and validation for internal auth (use existing auth/jwt.py)
Role-based access control (use rbac-patterns skill)
API key management and rotation (use secret-management skill)
CSRF protection for form submissions (use csrf-protection skill)
Core Principles
The Three Laws of OAuth
PKCE Always : Every authorisation code flow must use PKCE (Proof Key for Code Exchange). The implicit flow is deprecated — never use it.
Validate Redirect URIs : Redirect URIs must be whitelisted and validated on every request. Open redirects are a critical vulnerability.
Tokens Are Secrets : Access and refresh tokens must never appear in URLs, logs, or client-side storage. Use httpOnly cookies or server-side session storage.
Pattern 1: Supabase Auth Provider Configuration
Google and GitHub Setup
export const oauthProviders = [
{
provider : "google" as const ,
label : "Google" ,
scopes : "openid email profile" ,
queryParams : {
access_type : "offline" ,
prompt : "consent" ,
},
},
{
provider : "github" as const ,
label : "GitHub" ,
scopes : "read:user user:email" ,
},
] as const ;
export type OAuthProvider = (typeof oauthProviders)[number ]["provider" ];
Project Reference : apps/web/components/auth/oauth-providers.tsx — the existing component renders Google and GitHub buttons. apps/web/app/auth/callback/route.ts — the callback handler exchanges the authorisation code for a Supabase session.
Pattern 2: Authorisation Code Flow with PKCE
Initiating the Flow import { createClient } from "@/lib/supabase/client" ;
async function signInWithProvider (
provider : OAuthProvider ,
redirectTo ?: string ,
): Promise <void > {
const supabase = createClient ();
const { error } = await supabase.auth .signInWithOAuth ({
provider,
options : {
redirectTo : `${window .location.origin} /auth/callback${
redirectTo ? `?next=${encodeURIComponent (redirectTo)} ` : ""
} ` ,
queryParams : provider === "google"
? { access_type : "offline" , prompt : "consent" }
: undefined ,
},
});
if (error) {
throw new Error (`OAuth sign-in failed: ${error.message} ` );
}
}
Callback Handler
import { createClient } from "@/lib/supabase/server" ;
import { NextResponse } from "next/server" ;
export async function GET (request : Request ) {
const { searchParams, origin } = new URL (request.url );
const code = searchParams.get ("code" );
const next = searchParams.get ("next" ) ?? "/dashboard" ;
const error = searchParams.get ("error" );
if (error) {
const description = searchParams.get ("error_description" ) ?? error;
return NextResponse .redirect (
`${origin} /login?error=${encodeURIComponent (description)} ` ,
);
}
if (!code) {
return NextResponse .redirect (
`${origin} /login?error=${encodeURIComponent ("No authorisation code" )} ` ,
);
}
const supabase = await createClient ();
const { error : exchangeError } =
await supabase.auth .exchangeCodeForSession (code);
if (exchangeError) {
return NextResponse .redirect (
`${origin} /login?error=${encodeURIComponent (exchangeError.message)} ` ,
);
}
return NextResponse .redirect (`${origin} ${next} ` );
}
Rule : The code parameter is single-use. If the exchange fails, redirect to login with the error — never retry code exchange.
Pattern 3: Token Management
Secure Token Storage and Refresh import { createClient } from "@/lib/supabase/client" ;
export async function getSession ( ) {
const supabase = createClient ();
const { data : { session }, error } = await supabase.auth .getSession ();
if (error || !session) {
return null ;
}
const expiresAt = session.expires_at ?? 0 ;
const now = Math .floor (Date .now () / 1000 );
if (expiresAt - now < 60 ) {
const { data : { session : refreshed } } =
await supabase.auth .refreshSession ();
return refreshed;
}
return session;
}
export function onAuthStateChange (
callback : (event: string , session: unknown ) => void ,
) {
const supabase = createClient ();
const { data : { subscription } } = supabase.auth .onAuthStateChange (
(event, session ) => {
callback (event, session);
},
);
return subscription;
}
Rule : Never store tokens in localStorage. Supabase client handles storage via httpOnly cookies when configured with the server-side client.
Pattern 4: Account Linking
Multiple Providers per User async function linkProvider (provider : OAuthProvider ): Promise <void > {
const supabase = createClient ();
const { error } = await supabase.auth .linkIdentity ({
provider,
options : {
redirectTo : `${window .location.origin} /auth/callback?next=/settings` ,
},
});
if (error) {
throw new Error (`Account linking failed: ${error.message} ` );
}
}
async function unlinkProvider (identityId : string ): Promise <void > {
const supabase = createClient ();
const { error } = await supabase.auth .unlinkIdentity ({
id : identityId,
});
if (error) {
throw new Error (`Unlink failed: ${error.message} ` );
}
}
async function getLinkedProviders ( ): Promise <string []> {
const supabase = createClient ();
const { data : { user } } = await supabase.auth .getUser ();
if (!user?.identities ) return [];
return user.identities .map ((i ) => i.provider );
}
Pattern 5: Backend Token Validation (FastAPI)
Verifying Supabase JWT on API Requests from fastapi import Depends, HTTPException, Request
from jose import jwt, JWTError
SUPABASE_JWT_SECRET = settings.SUPABASE_JWT_SECRET
async def get_current_user_from_oauth (request: Request ):
"""Validate Supabase JWT from the Authorization header."""
auth_header = request.headers.get("authorization" , "" )
if not auth_header.startswith("Bearer " ):
raise HTTPException(status_code=401 , detail="Missing bearer token" )
token = auth_header.removeprefix("Bearer " )
try :
payload = jwt.decode(
token,
SUPABASE_JWT_SECRET,
algorithms=["HS256" ],
audience="authenticated" ,
)
except JWTError:
raise HTTPException(status_code=401 , detail="Invalid token" )
user_id = payload.get("sub" )
if not user_id:
raise HTTPException(status_code=401 , detail="Invalid token claims" )
user = await get_or_create_user(user_id, payload)
return user
Complements : rbac-patterns skill — after extracting the user from the JWT, apply permission checks via require_permission().
Pattern 6: Redirect URI Security
Whitelist Validation const ALLOWED_REDIRECT_HOSTS = new Set ([
"localhost" ,
"127.0.0.1" ,
process.env .NEXT_PUBLIC_APP_URL
? new URL (process.env .NEXT_PUBLIC_APP_URL ).hostname
: "" ,
].filter (Boolean ));
function isValidRedirectUri (uri : string ): boolean {
try {
const url = new URL (uri);
return ALLOWED_REDIRECT_HOSTS .has (url.hostname );
} catch {
return uri.startsWith ("/" ) && !uri.startsWith ("//" );
}
}
Rule : Always validate the next or redirectTo parameter against the whitelist. Open redirect attacks use OAuth callbacks to phish users.
Anti-Patterns Pattern Problem Correct Approach Implicit flow (no PKCE) Token exposed in URL fragment Authorisation code + PKCE Tokens in localStorage XSS can steal tokens httpOnly cookies via Supabase No redirect URI validation Open redirect vulnerability Whitelist allowed hosts Retry failed code exchange Code is single-use, replay attack risk Redirect to login on failure Hardcoded client secrets in frontend Secret exposed in bundle Server-side only, env variables No account linking Users create duplicate accounts Support multiple providers per user
Checklist Before merging oauth-flow changes:
Response Format When applying this skill, structure implementation as:
### OAuth Flow Implementation
**Flow** : [authorisation code + PKCE / device code]
**Providers** : [Google, GitHub / custom]
**Auth Library** : [Supabase Auth / NextAuth / custom]
**Token Storage** : [httpOnly cookies / server session]
**Account Linking** : [enabled / disabled]
**Redirect Validation** : [whitelist / regex / none]