The OAuth Worker implements OAuth 2.0 authorization server for third-party application integration.
Base URL: http://localhost:8788 (dev) | https://oauth.edgeauth.com (prod)
Database: edgeauth-oauth
EdgeAuth supports the Authorization Code Grant flow with PKCE (Proof Key for Code Exchange).
1. Client redirects user to /oauth/authorize
2. User authorizes the application
3. Server redirects back with authorization code
4. Client exchanges code for access token via /oauth/token
5. Client uses access token to access protected resources
Endpoint: GET /health
Response:
{
"status": "ok",
"service": "oauth-worker"
}Register a new OAuth client application.
Endpoint: POST /oauth/clients
Headers:
Authorization: Bearer <admin_jwt_token>Request Body:
{
"name": "My Application",
"description": "Application description",
"redirectUris": [
"https://myapp.com/callback",
"http://localhost:3000/callback"
],
"scopes": ["read", "write"],
"grantTypes": ["authorization_code", "refresh_token"]
}Success Response: 201 Created
{
"id": "client-uuid",
"secret": "client-secret-abc123",
"name": "My Application",
"description": "Application description",
"redirectUris": ["https://myapp.com/callback"],
"scopes": ["read", "write"],
"grantTypes": ["authorization_code", "refresh_token"],
"createdAt": 1234567890000,
"updatedAt": 1234567890000
}Important: Save the client_secret - it cannot be retrieved later.
Retrieve OAuth client information.
Endpoint: GET /oauth/clients/:clientId
Headers:
Authorization: Bearer <admin_jwt_token>Success Response: 200 OK
{
"id": "client-uuid",
"name": "My Application",
"description": "Application description",
"redirectUris": ["https://myapp.com/callback"],
"scopes": ["read", "write"],
"grantTypes": ["authorization_code"],
"createdAt": 1234567890000,
"updatedAt": 1234567890000
}Note: Client secret is never returned in GET requests.
List all registered OAuth clients.
Endpoint: GET /oauth/clients
Headers:
Authorization: Bearer <admin_jwt_token>Success Response: 200 OK
{
"clients": [
{
"id": "client-uuid-1",
"name": "App 1",
"scopes": ["read"],
"createdAt": 1234567890000
},
{
"id": "client-uuid-2",
"name": "App 2",
"scopes": ["read", "write"],
"createdAt": 1234567891000
}
]
}Update OAuth client configuration.
Endpoint: PATCH /oauth/clients/:clientId
Headers:
Authorization: Bearer <admin_jwt_token>Request Body:
{
"name": "Updated App Name",
"redirectUris": ["https://newdomain.com/callback"],
"scopes": ["read", "write", "admin"]
}Success Response: 200 OK
Delete an OAuth client and revoke all its tokens.
Endpoint: DELETE /oauth/clients/:clientId
Headers:
Authorization: Bearer <admin_jwt_token>Success Response: 204 No Content
Initiate OAuth authorization flow.
Endpoint: GET /oauth/authorize
Query Parameters:
response_type=code (required)
client_id=<client_id> (required)
redirect_uri=<callback> (required)
scope=read write (optional, space-separated)
state=<random_string> (recommended)
code_challenge=<challenge> (required for PKCE)
code_challenge_method=S256 (required for PKCE)
Example:
GET /oauth/authorize?response_type=code&client_id=abc123&redirect_uri=https://app.com/callback&scope=read&state=xyz&code_challenge=E9Melhoa...&code_challenge_method=S256
Flow:
- User is redirected to login (if not authenticated)
- User sees authorization consent screen
- User approves/denies access
- Server redirects to
redirect_uriwith authorization code
Success Redirect:
https://app.com/callback?code=auth_code_xyz&state=xyz
Error Redirect:
https://app.com/callback?error=access_denied&error_description=User denied access
Exchange authorization code for access token.
Endpoint: POST /oauth/token
Request Body (Authorization Code Grant):
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=<authorization_code>&
client_id=<client_id>&
client_secret=<client_secret>&
redirect_uri=<redirect_uri>&
code_verifier=<verifier>Success Response: 200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "refresh_token_xyz",
"scope": "read write"
}Request Body (Refresh Token Grant):
grant_type=refresh_token&
refresh_token=<refresh_token>&
client_id=<client_id>&
client_secret=<client_secret>Error Response: 400 Bad Request
{
"error": "invalid_grant",
"error_description": "Authorization code expired"
}Revoke an access or refresh token.
Endpoint: POST /oauth/revoke
Request Body:
Content-Type: application/x-www-form-urlencoded
token=<token_to_revoke>&
client_id=<client_id>&
client_secret=<client_secret>Success Response: 200 OK
{
"revoked": true
}PKCE (Proof Key for Code Exchange) adds security for public clients.
// Generate code verifier (random string)
const codeVerifier = generateRandomString(128);
// Generate code challenge (SHA256 hash)
const codeChallenge = base64UrlEncode(sha256(codeVerifier));GET /oauth/authorize?
response_type=code&
client_id=abc123&
redirect_uri=https://app.com/callback&
code_challenge=E9Melhoa...&
code_challenge_method=S256
POST /oauth/token
grant_type=authorization_code&
code=<authorization_code>&
client_id=abc123&
redirect_uri=https://app.com/callback&
code_verifier=<original_verifier>interface OAuthClient {
id: string;
secret: string;
name: string;
description?: string;
redirectUris: string[];
scopes: string[];
grantTypes: ("authorization_code" | "client_credentials" | "refresh_token")[];
createdAt: number;
updatedAt: number;
}interface AuthorizationCode {
code: string;
clientId: string;
userId: string;
redirectUri: string;
scopes: string[];
codeChallenge?: string;
codeChallengeMethod?: "S256" | "plain";
expiresAt: number;
createdAt: number;
used: boolean;
}interface AccessToken {
token: string; // JWT
clientId: string;
userId: string;
scopes: string[];
expiresAt: number;
createdAt: number;
}interface RefreshToken {
token: string;
clientId: string;
userId: string;
scopes: string[];
expiresAt: number;
createdAt: number;
revoked: boolean;
}- Always use PKCE for public clients (mobile, SPA)
- Validate redirect URIs strictly
- Use HTTPS in production
- Store client secrets securely
- Rotate refresh tokens periodically
- Implement rate limiting on token endpoint
- Use short-lived access tokens (1 hour default)
- Validate state parameter to prevent CSRF
OAuth 2.0 standard error codes:
| Error | Description |
|---|---|
invalid_request |
Missing or invalid parameter |
unauthorized_client |
Client not authorized for this grant type |
access_denied |
User denied authorization |
unsupported_response_type |
Server doesn't support response type |
invalid_scope |
Requested scope invalid or unknown |
server_error |
Internal server error |
invalid_grant |
Authorization code invalid/expired |
invalid_client |
Client authentication failed |
# 1. Register OAuth client
curl -X POST http://localhost:8788/oauth/clients \
-H "Authorization: Bearer <admin_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "My App",
"redirectUris": ["http://localhost:3000/callback"],
"scopes": ["read", "write"],
"grantTypes": ["authorization_code", "refresh_token"]
}'
# Save client_id and client_secret
# 2. Generate PKCE challenge
# code_verifier = random_string_128_chars
# code_challenge = base64url(sha256(code_verifier))
# 3. Redirect user to authorization endpoint
# Browser: GET /oauth/authorize?response_type=code&client_id=...&redirect_uri=...&code_challenge=...&code_challenge_method=S256
# 4. After user authorizes, exchange code for token
curl -X POST http://localhost:8788/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=AUTH_CODE&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&redirect_uri=http://localhost:3000/callback&code_verifier=VERIFIER"
# 5. Use access token
curl -X GET http://localhost:8789/auth/me \
-H "Authorization: Bearer ACCESS_TOKEN"
# 6. Refresh token when expired
curl -X POST http://localhost:8788/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token&refresh_token=REFRESH_TOKEN&client_id=CLIENT_ID&client_secret=CLIENT_SECRET"