OAuth 2.1 Authorization Server
BrewHoard's built-in OAuth 2.1 authorization server for MCP clients and third-party integrations.
Overview
BrewHoard includes a fully compliant OAuth 2.1 Authorization Server built on @jmondi/oauth2-server. It supports:
- Dynamic Client Registration (RFC 7591)
- Authorization Code + PKCE flow (required for public clients)
- Access & Refresh Tokens with scope-based permissions
- Token Revocation (RFC 7009)
The server is used primarily by the MCP server (/api/mcp) to authenticate AI assistants, but can be used by any third-party application.
Endpoints
Dynamic Client Registration
POST /oauth/register
Register a new OAuth client dynamically.
{
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:3000/callback"],
"grant_types": ["authorization_code"],
"token_endpoint_auth_method": "none",
"scope": "beers:read collection:read ratings:write"
}Response:
{
"client_id": "generated-client-id",
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:3000/callback"],
"grant_types": ["authorization_code"],
"token_endpoint_auth_method": "none",
"scope": "beers:read collection:read ratings:write"
}Authorization
GET /oauth/authorize
Start the authorization flow. The user must be logged in and will see a consent screen.
Query Parameters:
| Parameter | Required | Description |
|---|---|---|
response_type | Yes | Must be code |
client_id | Yes | Client ID from registration |
redirect_uri | Yes | Must match registered URI |
scope | Yes | Space-separated scopes |
state | Recommended | CSRF protection |
code_challenge | Yes (PKCE) | S256 or plain challenge |
code_challenge_method | Yes (PKCE) | S256 or plain |
Flow:
- User is redirected to the login page if not authenticated
- User sees a consent screen listing requested scopes
- On approval, user is redirected to
redirect_uri?code=AUTH_CODE&state=STATE
Consent
GET /oauth/consent
The consent page where users review and approve scope requests. Rendered as a Svelte page with the app name and requested permissions.
POST /oauth/consent/approve
Approve the authorization request.
Token Exchange
POST /oauth/token
Exchange an authorization code for access and refresh tokens.
Request:
{
"grant_type": "authorization_code",
"code": "authorization-code",
"redirect_uri": "http://localhost:3000/callback",
"client_id": "your-client-id",
"code_verifier": "pkce-verifier"
}Response:
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBh...",
"scope": "beers:read collection:read"
}Token Revocation
POST /oauth/revoke
Revoke an access or refresh token.
{
"token": "access-or-refresh-token",
"token_type_hint": "access_token"
}Client Revocation
POST /api/v1/oauth/revoke-client
Revoke all tokens for a specific OAuth client. Requires authentication.
{
"clientId": "client-id-to-revoke"
}Discovery
Authorization Server Metadata
GET /.well-known/oauth-authorization-server
Returns OAuth 2.0 Authorization Server Metadata (RFC 8414):
{
"issuer": "http://localhost:5173",
"authorization_endpoint": "http://localhost:5173/oauth/authorize",
"token_endpoint": "http://localhost:5173/oauth/token",
"registration_endpoint": "http://localhost:5173/oauth/register",
"revocation_endpoint": "http://localhost:5173/oauth/revoke",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_post"],
"code_challenge_methods_supported": ["S256"],
"scopes_supported": [
"beers:read", "beers:search", "beers:styles",
"collection:read", "collection:write", "collection:consume", "collection:history",
"ratings:write",
"recommendations:read",
"analytics:read",
"export"
]
}Available Scopes
| Scope | Description |
|---|---|
beers:read | View beer details |
beers:search | Search for beers |
beers:styles | List beer styles |
collection:read | View your collection |
collection:write | Add/remove collection items |
collection:consume | Mark beers as consumed |
collection:history | View consumption history |
ratings:write | Create and update ratings |
recommendations:read | Get personalized recommendations |
analytics:read | View collection statistics and trends |
export | Export collection data |
Database Schema
OAuth data is stored in dedicated tables:
| Table | Purpose |
|---|---|
oauth_scope | Available OAuth scopes |
oauth_client | Registered applications |
oauth_client_scope | Client ↔ scope mappings |
oauth_auth_code | Authorization codes (short-lived) |
oauth_auth_code_scope | Auth code ↔ scope mappings |
oauth_token | Access & refresh tokens |
oauth_token_scope | Token ↔ scope mappings |
All tokens and auth codes support revocation. Expired entries should be cleaned up periodically.
Security Considerations
- PKCE is required for all public clients (no
client_secret) - Authorization codes expire after 60 seconds and are single-use
- Access tokens expire after 1 hour
- Refresh tokens can be exchanged for new access tokens
- All tokens can be revoked individually or per-client
- Scope changes require re-authorization
Next Steps
- MCP Server — Use OAuth tokens with the MCP server
- API Reference — REST API documentation
- Authentication — Session and API key authentication