OAuth2 Provider
Run Authsome as an OAuth2 authorization server with Authorization Code, PKCE, and Client Credentials flows.
Authsome can act as a full OAuth2 authorization server, allowing third-party applications to authenticate users and obtain access tokens through standard OAuth2 flows. This is distinct from Social Login (where Authsome is an OAuth2 client) — here, Authsome issues its own tokens on behalf of your users.
Setup
import (
"github.com/xraph/authsome"
"github.com/xraph/authsome/plugins/oauth2provider"
)
eng, err := authsome.New(
authsome.WithStore(store),
authsome.WithPlugin(password.New()),
authsome.WithPlugin(oauth2provider.New()),
authsome.WithConfig(authsome.Config{
AppID: "myapp",
OAuth2: authsome.OAuth2Config{
Enabled: true,
Issuer: "https://auth.myapp.com",
AuthorizationEndpoint: "/oauth2/authorize",
TokenEndpoint: "/oauth2/token",
IntrospectionEndpoint: "/oauth2/introspect",
RevocationEndpoint: "/oauth2/revoke",
JWKSEndpoint: "/.well-known/jwks.json",
AccessTokenTTL: "1h",
RefreshTokenTTL: "30d",
AuthorizationCodeTTL: "10m",
RequirePKCE: true,
AllowedScopes: []string{"openid", "profile", "email", "offline_access"},
},
}),
)Client registration
OAuth2 clients must be registered before they can request tokens. Clients represent third-party applications or services that consume your authentication API.
Create a client
POST /v1/auth/oauth2/clients
{
"name": "My Mobile App",
"type": "public",
"redirect_uris": ["myapp://callback", "https://myapp.com/callback"],
"scopes": ["openid", "profile", "email"],
"grant_types": ["authorization_code"],
"app_id": "myapp"
}Response:
{
"client_id": "aoc2_01jb...",
"client_secret": "sec_a3f8c9d4e5f2g7h1...",
"name": "My Mobile App",
"type": "public",
"redirect_uris": ["myapp://callback", "https://myapp.com/callback"],
"scopes": ["openid", "profile", "email"],
"grant_types": ["authorization_code"],
"created_at": "2024-11-01T10:00:00Z"
}Important: The
client_secretis only returned once at creation time. Store it securely. Public clients (mobile/SPA apps) should use PKCE instead of a client secret.
Client types
| Type | Description | Use case |
|---|---|---|
confidential | Can securely store a client secret | Server-side applications, backend services |
public | Cannot securely store a client secret | Mobile apps, single-page applications, CLI tools |
In Go
client := &oauth2provider.Client{
Name: "My Mobile App",
Type: "public",
RedirectURIs: []string{"myapp://callback"},
Scopes: []string{"openid", "profile", "email"},
GrantTypes: []string{"authorization_code"},
}
err := engine.CreateOAuth2Client(ctx, appID, client)
// client.ID and client.Secret are now populatedAuthorization Code flow with PKCE
The Authorization Code flow with PKCE (Proof Key for Code Exchange) is the recommended flow for all client types. It is required for public clients and strongly recommended for confidential clients.
Generate PKCE challenge: The client generates a random code_verifier and derives a code_challenge from it.
// Client-side
import "crypto/sha256"
import "encoding/base64"
codeVerifier := generateRandomString(64)
hash := sha256.Sum256([]byte(codeVerifier))
codeChallenge := base64.RawURLEncoding.EncodeToString(hash[:])Authorization request: Redirect the user to the authorization endpoint.
GET /v1/auth/oauth2/authorize?
response_type=code&
client_id=aoc2_01jb...&
redirect_uri=https://myapp.com/callback&
scope=openid+profile+email&
state=random_state_value&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256If the user is not already signed in, they are redirected to the Authsome login page. After authentication (and MFA if enabled), the user is shown a consent screen listing the requested scopes. On approval, the user is redirected back to the client with an authorization code.
HTTP/1.1 302 Found
Location: https://myapp.com/callback?code=auth_01jb...&state=random_state_valueToken exchange: The client exchanges the authorization code for tokens.
POST /v1/auth/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=auth_01jb...&
redirect_uri=https://myapp.com/callback&
client_id=aoc2_01jb...&
code_verifier=the_original_code_verifier_stringResponse:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_d72b1ef8...",
"scope": "openid profile email",
"id_token": "eyJhbGciOiJSUzI1NiIs..."
}Client Credentials grant
The Client Credentials grant is used for machine-to-machine authentication where no user context is needed. Only confidential clients can use this grant type.
POST /v1/auth/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=aoc2_01jb...&
client_secret=sec_a3f8c9d4e5f2g7h1...&
scope=api:read api:writeResponse:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "api:read api:write"
}No refresh token is issued for client credentials grants — the client simply requests a new token when the current one expires.
Token introspection
Resource servers can verify tokens by calling the introspection endpoint. This is useful for opaque tokens or when the resource server does not have access to the signing keys.
POST /v1/auth/oauth2/introspect
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
token=eyJhbGciOiJSUzI1NiIs...Response (active token):
{
"active": true,
"sub": "ausr_01j9...",
"client_id": "aoc2_01jb...",
"scope": "openid profile email",
"iat": 1730451600,
"exp": 1730455200,
"iss": "https://auth.myapp.com",
"token_type": "Bearer"
}Response (expired or revoked token):
{
"active": false
}Token revocation
Clients can revoke tokens when they are no longer needed (e.g., on user logout):
POST /v1/auth/oauth2/revoke
Content-Type: application/x-www-form-urlencoded
token=eyJhbGciOiJSUzI1NiIs...&
token_type_hint=access_token&
client_id=aoc2_01jb...&
client_secret=sec_a3f8c9d4e5f2g7h1...The endpoint always returns 200 OK regardless of whether the token was found, to prevent token enumeration.
Scopes and consent
Built-in scopes
| Scope | Description |
|---|---|
openid | Required for OIDC. Returns an ID token with the sub claim |
profile | Access to user's name, username, and profile image |
email | Access to user's email address and email verification status |
phone | Access to user's phone number and phone verification status |
offline_access | Request a refresh token for long-lived access |
Custom scopes
Register custom scopes in the configuration:
OAuth2: authsome.OAuth2Config{
AllowedScopes: []string{
"openid", "profile", "email",
"api:read", "api:write", "admin",
},
},Consent screen
When a third-party client requests authorization, the user is shown a consent screen listing the requested scopes. The consent decision is remembered per user-client pair. To force re-consent:
GET /v1/auth/oauth2/authorize?...&prompt=consentJWKS endpoint
The JSON Web Key Set (JWKS) endpoint publishes the public keys used to verify JWT access tokens and ID tokens:
GET /.well-known/jwks.json
{
"keys": [
{
"kty": "RSA",
"kid": "key-2024-01",
"use": "sig",
"alg": "RS256",
"n": "0vx7agoebGc...",
"e": "AQAB"
}
]
}Resource servers should cache the JWKS and refresh it periodically or when they encounter a token signed with an unknown kid.
OpenID Connect discovery
Authsome publishes an OpenID Connect discovery document:
GET /.well-known/openid-configuration
{
"issuer": "https://auth.myapp.com",
"authorization_endpoint": "https://auth.myapp.com/v1/auth/oauth2/authorize",
"token_endpoint": "https://auth.myapp.com/v1/auth/oauth2/token",
"introspection_endpoint": "https://auth.myapp.com/v1/auth/oauth2/introspect",
"revocation_endpoint": "https://auth.myapp.com/v1/auth/oauth2/revoke",
"jwks_uri": "https://auth.myapp.com/.well-known/jwks.json",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"scopes_supported": ["openid", "profile", "email", "phone", "offline_access"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "none"],
"code_challenge_methods_supported": ["S256"]
}Configuration reference
| Option | Type | Default | Description |
|---|---|---|---|
Enabled | bool | false | Enable OAuth2 provider functionality |
Issuer | string | "" | Issuer identifier URL (must be HTTPS in production) |
AccessTokenTTL | string | "1h" | Access token lifetime |
RefreshTokenTTL | string | "30d" | Refresh token lifetime |
AuthorizationCodeTTL | string | "10m" | Authorization code validity |
RequirePKCE | bool | true | Require PKCE for all authorization code grants |
AllowedScopes | []string | ["openid", "profile", "email"] | Scopes that clients can request |
API routes
| Method | Path | Description |
|---|---|---|
GET | /oauth2/authorize | Authorization endpoint (redirects user) |
POST | /oauth2/token | Token endpoint (code exchange, client credentials, refresh) |
POST | /oauth2/introspect | Token introspection |
POST | /oauth2/revoke | Token revocation |
POST | /oauth2/clients | Register a new OAuth2 client |
GET | /oauth2/clients | List registered clients |
GET | /oauth2/clients/:clientId | Get client details |
PATCH | /oauth2/clients/:clientId | Update a client |
DELETE | /oauth2/clients/:clientId | Delete a client |
GET | /.well-known/jwks.json | JWKS endpoint |
GET | /.well-known/openid-configuration | OIDC discovery document |