OAuth Integration Guide

This document explains how PAM integrates GitHub, Google and other third-party sign-in via Supabase Auth, and how third-party apps connect to its standard OAuth 2.0 authorization endpoints.

Architecture: authentication client and OAuth authorization

PAM connects to Supabase Auth via SupabaseOAuthProvider, supporting GitHub, Google sign-in and email/password, phone OTP. It also provides standard OAuth 2.0 endpoints (authorize, consent, token, PKCE, userinfo) via @qlover/oauth-wrapper for third-party app integration.

Current deployment: Supabase Auth

Users can sign in via GitHub, Google, or email/password (POST /api/user/login), phone OTP, email Magic Link. OAuth authorization endpoints are independent of the sign-in method. See .env.template for SUPABASE_URL, SESSION_SECRET, ENCRYPTION_KEY, etc.

Protocol overview

Exposes the OAuth 2.0 authorization code grant (RFC 6749) for confidential and public clients (PKCE for public). The access_token returned to clients comes from Supabase Auth (JWT); the platform also issues its own refresh_token for third-party refresh grants.

Authorization code flow

  1. Create an app in the developer console and register redirect_uri values (HTTPS required except localhost).
  2. Redirect the user to GET /oauth/authorize with client_id, redirect_uri, response_type=code; public clients must include PKCE parameters.
  3. The end user signs in on this site (GitHub, Google, or email/password), grants consent, then the browser returns to redirect_uri with ?code=...&state=....
  4. Your backend calls POST /oauth/token with the code to obtain access_token (and optional refresh_token).
  5. Call GET /userinfo with Bearer access_token to load the user profile.

Endpoints

MethodPathRoleNotes
POST/api/oauth/verifyEnd-user sign-in (server)Email/password sign-in for the consent UI session; orchestrated by Supabase Auth—not for OAuth clients.
GET/oauth/authorizeAuthorize (browser)Consent UI; unauthenticated users are sent to login then returned here.
POST/oauth/tokenToken (server)grant_type=authorization_code or refresh_token; client auth via Basic or form body.
GET/oauth/userinfoUserinfo (server)Requires Authorization: Bearer; returns sub, name (prefers display_name), business email (no @phone.pam.local), optional phone_number.

Authorization request

Required: response_type=code, client_id, redirect_uri (must match a registered URI). Optional: scope, state, ui_locales (or locale, for consent UI language, e.g. zh/en); public clients require code_challenge and code_challenge_method=S256.

GET /oauth/authorize?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fapp.example%2Fcallback
  &scope=openid%20profile
  &state=RANDOM_STATE
  &code_challenge=CHALLENGE
  &code_challenge_method=S256

Token exchange

Exchange authorization code

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https%3A%2F%2Fapp.example%2Fcallback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&code_verifier=VERIFIER

Refresh token

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET

// 200 OK { "access_token": "...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "...", "scope": "openid profile" }

PKCE (public clients)

Generate code_verifier before authorize, pass its S256 digest as code_challenge, then send code_verifier when exchanging the code. Confidential clients may omit PKCE.

Userinfo

Success returns JSON with sub (user id), name (prefers pam_users.display_name), email (real business email only; may be empty for phone login), email_verified, and phone_number when present. Invalid or expired tokens yield 401 with error=invalid_token. Internal @phone.pam.local placeholders are never returned.

GET /oauth/userinfo
Authorization: Bearer ACCESS_TOKEN

Error responses

Authorization errors redirect with error and error_description query params; token and userinfo errors are JSON (e.g. invalid_request, invalid_grant, invalid_client, invalid_token).