Agents OAuth2 JWT Bearer
A PartyServer mixin for adding OAuth 2.0 JWT Bearer Token authentication to your PartyServer applications.
It should work with:
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
yarn add agents-oauth2-jwt-bearer
pnpm add agents-oauth2-jwt-bearer
Usage
Basic Example
import { Server } from "partyserver";
import { WithAuth } from "agents-oauth2-jwt-bearer";
type MyEnv = {
OIDC_ISSUER_URL: string;
OIDC_AUDIENCE: string;
};
class MyAuthenticatedServer extends WithAuth(Server<MyEnv>) {
}
class MyAuthenticatedServer extends WithAuth(Server, {
verify: verifyOptions,
authRequired: false,
}) {
}
const server = new MyAuthenticatedServer({
env: {
OIDC_ISSUER_URL: "https://your-identity-provider.com",
OIDC_AUDIENCE: "your-api-audience",
},
});
server.start();
Accessing User Info
Once you've added the mixin, you can access token information and claims:
class MyAuthenticatedServer extends WithAuth(Server<MyEnv>) {
onAuthenticatedRequest(req: Request) {
const claims = this.getClaims();
if (claims.sub !== expectedUserId) {
return new Response("You are not welcome", { status: 401 });
}
}
onRequest(req: Request) {
const tokenSet = this.getCredentials();
const claims = this.getClaims();
console.log(`User ID: ${claims.sub}`);
return new Response("Hello authenticated user!");
}
onAuthenticatedConnect(connection: Connection, ctx: ConnectionContext) {
const claims = this.getClaims();
if (claims.sub !== expectedUserId) {
connection.close(1008, "I don't like you");
}
}
onConnect(connection: Connection, ctx: ConnectionContext) {
const tokenSet = this.getCredentials();
const claims = this.getClaims();
console.log(`Connected user: ${claims.sub}`);
}
onMessage(connection: Connection, message: unknown) {
const tokenSet = this.getCredentials();
const claims = this.getClaims();
console.log(`Message from user: ${claims.sub}`);
}
}
Authentication Flow
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 extends Server
.
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
TokenSet
object containing:
access_token
: The JWT bearer token
id_token
: Optional ID token (from x-id-token
header)
refresh_token
: Optional refresh token (from x-refresh-token
header)
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
class MyServer extends WithOwnership(WithAuth(Server<MyEnv>), {
debug: (message, ctx) => console.log(message, ctx),
}) {
async onAuthorizedConnect(connection, ctx) {
console.log("Owner connected:", this.getClaims()?.sub);
}
async onAuthorizedRequest(req) {
console.log("Owner made a request:", this.getClaims()?.sub);
}
}
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 owner
overwrite
: Optional boolean to allow overwriting an existing owner
Example:
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
WithAuth
mixin
- Then, the ownership check verifies if the authenticated user is the owner
-
If the ownership check succeeds:
- The
onAuthorizedConnect
or onAuthorizedRequest
method is called
- The connection or request is allowed to proceed
-
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
License
MIT 2025 - José Romaniello