agents-oauth2-jwt-bearer
v3.3.1
Published
A PartyServer mixin for adding OAuth 2.0 JWT Bearer Token authentication to your PartyServer applications.
Readme
Agents OAuth2 JWT Bearer
A PartyServer mixin for adding OAuth 2.0 JWT Bearer Token authentication to your PartyServer applications.
It should work with:
- PartyKit: https://docs.partykit.io/guides/authentication/
- Cloudflare Agents: https://agents.cloudflare.com/
Overview
This package provides a mixin that adds authentication functionality to a PartyServer server using JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens. It allows you to secure your PartyServer applications by validating access tokens from requests and connections.
Installation
npm install agents-oauth2-jwt-bearer
# or
yarn add agents-oauth2-jwt-bearer
# or
pnpm add agents-oauth2-jwt-bearerUsage
Basic Example
import { Server } from "partyserver";
import { WithAuth } from "agents-oauth2-jwt-bearer";
// Define your environment type
type MyEnv = {
OIDC_ISSUER_URL: string;
OIDC_AUDIENCE: string;
// ... other environment variables
};
// Create your server class with authentication
class MyAuthenticatedServer extends WithAuth(Server<MyEnv>) {
// Your server implementation
}
// Pass verify options as parameters to the mixin function
class MyAuthenticatedServer extends WithAuth(Server, {
// Optional: specify the audience and issuer, etc
verify: verifyOptions,
// Optional: allow unauthenticated requests and connections
authRequired: false,
}) {
// Your server implementation
}
// Start the server
const server = new MyAuthenticatedServer({
env: {
OIDC_ISSUER_URL: "https://your-identity-provider.com",
OIDC_AUDIENCE: "your-api-audience",
// ... other environment variables
},
});
server.start();Accessing User Info
Once you've added the mixin, you can access token information and claims:
class MyAuthenticatedServer extends WithAuth(Server<MyEnv>) {
//optionally override onAuthenticatedRequest
onAuthenticatedRequest(req: Request) {
// Get the JWT claims from the token
const claims = this.getClaims();
if (claims.sub !== expectedUserId) {
return new Response("You are not welcome", { status: 401 });
}
}
onRequest(req: Request) {
// Get the token set from the request
const tokenSet = this.getCredentials();
// Get the Access Token claims from the token
const claims = this.getClaims();
// Now you can use the claims to identify the user
console.log(`User ID: ${claims.sub}`);
// Continue processing the request...
return new Response("Hello authenticated user!");
}
//optionally override onAuthenticatedConnect
onAuthenticatedConnect(connection: Connection, ctx: ConnectionContext) {
// Get the JWT claims from the token
const claims = this.getClaims();
if (claims.sub !== expectedUserId) {
connection.close(1008, "I don't like you");
}
}
onConnect(connection: Connection, ctx: ConnectionContext) {
// Get the token set from the connection
const tokenSet = this.getCredentials();
// Get the JWT claims from the token
const claims = this.getClaims();
// Use the claims in your connection handling logic
console.log(`Connected user: ${claims.sub}`);
}
onMessage(connection: Connection, message: unknown) {
// Get the token set from the connection
const tokenSet = this.getCredentials();
// Get the JWT claims from the token
const claims = this.getClaims();
// Use the claims in your message handling logic
console.log(`Message from user: ${claims.sub}`);
// Process the message...
}
}Authentication Flow
When a client makes a request or connection:
- The mixin extracts the bearer token from the Authorization header or the
access_tokenquery parameter - It validates the token using the JWKS from your identity provider
- It verifies the token's issuer and audience
- The mixin extracts the bearer token from the Authorization header or the
If validation succeeds:
- The request or connection proceeds
- Token information is stored for the connection
If validation fails:
- A 401 Unauthorized response is returned for HTTP requests
- The connection attempt is rejected
Configuration
The WithAuth mixin requires the following environment variables:
OIDC_ISSUER_URL: The URL of the OpenID Connect issuer (your identity provider)OIDC_AUDIENCE: The audience for the JWT, typically your API identifier
API Reference
WithAuth(BaseClass)
A mixin factory function that adds authentication functionality to a PartyServer class.
Parameters:
BaseClass: The base class to extend from. This should be a class that extendsServer.
Returns:
- A new class that extends the base class with authentication capabilities.
Methods
getCredentials(): TokenSet | void
Gets the token set associated with the current context.
Returns:
- A
TokenSetobject containing:access_token: The JWT bearer tokenid_token: Optional ID token (fromx-id-tokenheader)refresh_token: Optional refresh token (fromx-refresh-tokenheader)
getClaims(reqOrConnection: Request | Connection): Record<string, unknown>
Gets the decoded JWT claims from the access token.
Returns:
- An object containing the JWT claims
Token Format
The mixin accepts tokens in the following formats:
- Authorization header:
Authorization: Bearer <token> - Query parameter:
?access_token=<token>
Advanced Usage: WithOwnership Mixin
Overview
The WithOwnership mixin adds ownership capabilities to a PartyServer that already has authentication provided by the WithAuth mixin. This is particularly useful for scenarios where you need to restrict access to resources based on ownership, such as private chats or user-specific data.
Key Features
- Owner-based access control for connections and requests
- Integration with Durable Objects for persistent ownership data
- Automatic rejection of non-owner access attempts
Usage Example
// Then add ownership with WithOwnership
class MyServer extends WithOwnership(WithAuth(Server<MyEnv>), {
// Optional: provide a debug function
debug: (message, ctx) => console.log(message, ctx),
}) {
// Your server implementation
// Optionally override authorization methods
async onAuthorizedConnect(connection, ctx) {
console.log("Owner connected:", this.getClaims()?.sub);
// Handle authorized connection
}
async onAuthorizedRequest(req) {
console.log("Owner made a request:", this.getClaims()?.sub);
// Handle authorized request
}
}Ownership Methods
setOwner(owner: string, overwrite: boolean = false): Promise<void>
Sets the owner of the object. By default, it will throw an error if the owner is already set to a different user unless overwrite is set to true.
Parameters:
owner: The user ID (sub from JWT claims) to set as the owneroverwrite: Optional boolean to allow overwriting an existing owner
Example:
// When initializing a new chat or resource
async onCreate() {
const claims = this.getClaims();
if (claims?.sub) {
await this.setOwner(claims.sub);
}
}getOwner(): Promise<string | undefined>
Gets the current owner of the object.
Returns:
- The user ID (sub) of the owner, or undefined if no owner is set
Example:
async checkOwnership() {
const owner = await this.getOwner();
console.log(`This resource is owned by: ${owner}`);
}Authorization Flow
When a client makes a request or connection:
- First, the authentication checks are performed by the
WithAuthmixin - Then, the ownership check verifies if the authenticated user is the owner
- First, the authentication checks are performed by the
If the ownership check succeeds:
- The
onAuthorizedConnectoronAuthorizedRequestmethod is called - The connection or request is allowed to proceed
- The
If the ownership check fails:
- For WebSocket connections: Connection is closed with code 1008 and message "This chat is not yours."
- For HTTP requests: A 403 Forbidden response is returned with message "This chat is not yours."
DurableObject Integration
The WithOwnership mixin is designed to work with Cloudflare DurableObjects for storing ownership data. The mixin uses the DurableObject's storage API to persist ownership information.
Note: If you're not using DurableObjects, you'll need to override the setOwner and getOwner methods to implement your own storage mechanism.
References
- This project is similar to other Auth0 middlewares like node-oauth2-jwt-bearer.
- Authentication on PartyKit.
License
MIT 2025 - José Romaniello
