OAuth2 Provider Plugin
Turn Authsome into an OAuth2 authorization server with Authorization Code + PKCE, Client Credentials, OIDC discovery, and client management.
The oauth2provider plugin turns Authsome into a full OAuth2 authorization server. It implements the Authorization Code grant with PKCE (RFC 7636), Client Credentials grant, token revocation (RFC 7009), OIDC UserInfo, and OpenID Connect Discovery.
Setup
import (
"github.com/xraph/authsome"
"github.com/xraph/authsome/plugins/oauth2provider"
)
eng, err := authsome.NewEngine(
authsome.WithStore(store),
authsome.WithPlugin(oauth2provider.New(oauth2provider.Config{
Issuer: "https://auth.example.com",
AuthCodeTTL: 10 * time.Minute,
AccessTokenTTL: 1 * time.Hour,
})),
)Configuration
| Option | Type | Default | Description |
|---|---|---|---|
Issuer | string | "https://localhost" | OAuth2 issuer URL used in OIDC discovery |
AuthCodeTTL | time.Duration | 10m | Lifetime of authorization codes |
AccessTokenTTL | time.Duration | 1h | Lifetime of issued access tokens |
Implemented interfaces
| Interface | Purpose |
|---|---|
Plugin | Base plugin identity ("oauth2provider") |
OnInit | Captures store, logger, session config resolver, and token format resolver |
RouteProvider | Registers authorization, token, revoke, userinfo, discovery, and admin endpoints |
MigrationProvider | Contributes oauth2_clients and oauth2_auth_codes tables |
Authorization Code Grant with PKCE
The Authorization Code flow with PKCE is the recommended grant type for both public and confidential clients.
Step 1: Authorization request
GET /v1/auth/oauth/authorize
?response_type=code
&client_id=abc123
&redirect_uri=https://app.example.com/callback
&scope=openid profile email
&state=random-state
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256The user must be authenticated. The endpoint validates the client, redirect URI, and PKCE challenge, then redirects back with an authorization code.
Step 2: Token exchange
POST /v1/auth/oauth/token
{
"grant_type": "authorization_code",
"code": "the-authorization-code",
"redirect_uri": "https://app.example.com/callback",
"client_id": "abc123",
"client_secret": "secret",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}The plugin validates the code, checks PKCE, authenticates the client (for confidential clients), and issues tokens.
Response:
{
"access_token": "eyJhbGciOiJS...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "d72b1ef8...",
"scope": "openid profile email"
}PKCE verification
The plugin supports both S256 and plain challenge methods. S256 is the recommended method:
- The client generates a random
code_verifier - Computes
code_challenge = BASE64URL(SHA256(code_verifier)) - Sends
code_challengein the authorization request - Sends
code_verifierin the token request - The server verifies that
BASE64URL(SHA256(code_verifier)) == code_challenge
Client Credentials Grant
Machine-to-machine authentication for confidential clients:
POST /v1/auth/oauth/token
{
"grant_type": "client_credentials",
"client_id": "abc123",
"client_secret": "secret"
}Response:
{
"access_token": "a3f8c9d4...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile email"
}Client credentials tokens have no associated user. They are issued with an empty user ID for service-to-service communication.
Token revocation
POST /v1/auth/oauth/revoke
{
"token": "the-access-or-refresh-token",
"token_type_hint": "access_token"
}Follows RFC 7009. Always returns 200 OK regardless of whether the token was found.
OIDC UserInfo
GET /v1/auth/oauth/userinfo
Returns claims about the authenticated user (requires a valid access token):
{
"sub": "ausr_01j9...",
"email": "alice@example.com",
"email_verified": true,
"name": "Alice Liddell",
"phone": "+14155551234"
}OpenID Connect Discovery
GET /.well-known/openid-configuration
Returns the OIDC discovery document:
{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/v1/auth/oauth/authorize",
"token_endpoint": "https://auth.example.com/v1/auth/oauth/token",
"userinfo_endpoint": "https://auth.example.com/v1/auth/oauth/userinfo",
"revocation_endpoint": "https://auth.example.com/v1/auth/oauth/revoke",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "client_credentials"],
"scopes_supported": ["openid", "profile", "email", "phone"],
"code_challenge_methods_supported": ["S256", "plain"]
}Client management
Create a client
POST /v1/auth/admin/oauth/clients
{
"app_id": "aapp_01j9...",
"name": "My SPA",
"redirect_uris": ["https://app.example.com/callback"],
"scopes": ["openid", "profile", "email"],
"grant_types": ["authorization_code"],
"public": true
}Response:
{
"id": "aocl_01j9...",
"client_id": "abc123def456...",
"client_secret": "secret-shown-only-once",
"name": "My SPA",
"redirect_uris": ["https://app.example.com/callback"],
"scopes": ["openid", "profile", "email"],
"grant_types": ["authorization_code"],
"public": true
}The client_secret is only returned at creation time. It is bcrypt-hashed before storage. Public clients (SPAs, mobile apps) do not receive a secret.
List clients
GET /v1/auth/admin/oauth/clients?app_id=aapp_01j9...
Delete a client
DELETE /v1/auth/admin/oauth/clients/:clientId
API routes
| Method | Path | Description |
|---|---|---|
GET | /v1/auth/oauth/authorize | OAuth2 authorization endpoint |
POST | /v1/auth/oauth/token | Token endpoint (code exchange, client credentials) |
POST | /v1/auth/oauth/revoke | Token revocation |
GET | /v1/auth/oauth/userinfo | OIDC UserInfo |
GET | /.well-known/openid-configuration | OIDC Discovery |
POST | /v1/auth/admin/oauth/clients | Create OAuth2 client |
GET | /v1/auth/admin/oauth/clients | List OAuth2 clients |
DELETE | /v1/auth/admin/oauth/clients/:clientId | Delete OAuth2 client |
JWT access tokens
When the engine has a JWT token format configured for the app, the plugin issues JWT access tokens instead of opaque tokens. The JWT includes sub, app_id, session_id, scopes, iat, and exp claims.