#Alepha - Api Oauth
#Installation
Part of the alepha package. Import from alepha/api/oauth.
npm install alepha
#Overview
OAuth 2.1 authorization server module for MCP.
Features:
- OAuth 2.1 authorization code flow with PKCE (RFC 7636)
- Dynamic Client Registration (RFC 7591), deduplicated
- Authorization server metadata discovery (RFC 8414)
- Stateless authorization codes (short-lived signed JWTs)
- Single-use code enforcement
- Refresh tokens bound to the client they were issued to
- Device authorization grant (RFC 8628), with the page a human approves a device on
The device grant ships both halves. POST /oauth/device_authorization
and the device_code token grant are the device's; /oauth/device is the
human's, and it is the verification_uri a device is told to print. It is
server-rendered HTML like the consent screen, sends a signed-out visitor to
loginPath?redirect_uri= and back, and refuses an answer posted from
another origin - see OAuthController.deviceDecision for why that check
matters more here than on the consent POST.
Registration is deduplicated, and that is what makes a "connected app"
a thing. A client that registers again with the same name, the same
redirect_uris and no secret is handed the row it already has instead of a
new one. Some clients - claude.ai among them - run DCR on every connect
and never reuse an id, which grew a table of near-identical rows and, worse,
made one application look like four to anything grouping by client_id.
Reuse is refused for a confidential client, a revoked one, another realm,
a different redirect_uri set, and for any registration that named its own
client_id - see OAuthClientService.register.
oauth_clients.lastUsedAt is written on every successful grant, and
OAuthJobs.purgeAbandonedClients collects DCR rows older than a day that
never received a token (lastUsedAt null) and no session references. A
client used once is never collected: some clients (ChatGPT) register once
and reuse that client_id for the life of the connector. Register it the
way $realm does; a job that mounted itself would run in every
application that imports this module.
The refresh_token grant requires client_id. The client is looked up
and - when confidential - must present its secret, exactly as on the
authorization_code grant; the refresh token must then belong to a session
minted for that same client. A session with no recorded client (an ordinary
password login) is not an OAuth grant and cannot be refreshed here.
This makes the id_token aud trustworthy: it is the authenticated client,
not an unvalidated request field. Without the binding, any refresh-token
holder could name any client_id and receive an id_token minted for it,
which a relying party that forwards id_tokens as its Bearer would accept.
Which redirect URIs a client may register. https:// to any host, with
at most one * standing for a single host label
(https://*.example.com/cb), or plain http:// to the loopback interface
only: 127.0.0.1, [::1] or localhost, compared on the parsed host. A
loopback redirect is matched on any port (RFC 8252 §7.3), since a native
app listens wherever the OS lets it; everything else is matched exactly. A
refused registration answers 400 with an RFC 7591 body, error being
invalid_redirect_uri or invalid_client_metadata.
Integration: Register the module and configure the realm + protected resource path:
1const app = Alepha.create()2 .with(AlephaOAuth)3 .set(oauthOptions, { realm: "users", resource: "/mcp" });