Skip to main content

Authentication

Chive uses AT Protocol OAuth for user authentication and JWT tokens for session management. This page covers the authentication flows, token handling, and authorization.

Authentication methods

MethodUse caseHow it works
AT Protocol OAuthUser loginOAuth 2.0 + PKCE/DPoP via @atproto/oauth-client-browser
Service auth JWTServer-to-serverSigned JWTs for inter-service calls
Session tokensAPI requestsBearer tokens from OAuth flow

AT Protocol OAuth flow

Chive implements the AT Protocol OAuth specification using @atproto/oauth-client-browser. The OAuth flow is handled by the Next.js frontend, not the API server.

Implementation

The OAuth flow is implemented in the frontend using @atproto/oauth-client-browser:

// web/lib/auth/oauth-client.ts
import { BrowserOAuthClient } from '@atproto/oauth-client-browser';

// Initialize client (handles PKCE, DPoP, PAR automatically)
const client = await BrowserOAuthClient.load({
clientId: 'https://chive.pub/oauth/client-metadata.json',
handleResolver,
});

// Start login
const url = await client.authorize(handle, {
scope: 'atproto transition:generic',
});

// Handle callback (on return from auth server)
const result = await client.callback(params);
const session = result.session;

Session tokens

After successful OAuth, the frontend receives DPoP-bound tokens that are used for API requests.

Using access tokens

Include the access token in API requests:

GET /xrpc/pub.chive.actor.getMyProfile
Authorization: Bearer eyJhbGciOiJFUzI1NiIs...

Token structure

Access tokens are JWTs containing:

{
"iss": "https://api.chive.pub",
"sub": "did:plc:abc123...",
"aud": "https://api.chive.pub",
"exp": 1704416400,
"iat": 1704412800,
"scope": "read write",
"handle": "alice.bsky.social"
}
ClaimDescription
issToken issuer (Chive)
subUser's DID
audIntended audience
expExpiration timestamp
iatIssued-at timestamp
scopeGranted permissions
handleUser's current handle

Token refresh

Access tokens expire after 1 hour. Use the refresh token to get new access tokens:

POST /oauth/token
Content-Type: application/json

{
"grant_type": "refresh_token",
"refresh_token": "eyJhbGciOiJFUzI1NiIs..."
}

Response:

{
"access_token": "new-access-token...",
"refresh_token": "new-refresh-token...",
"token_type": "Bearer",
"expires_in": 3600
}

Token lifetimes

Token typeLifetimeRefresh behavior
Access token1 hourMust refresh before expiry
Refresh token30 daysRotated on each use
Session30 daysExtended on activity

Logout

Revoke tokens when the user logs out:

POST /oauth/revoke
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJFUzI1NiIs...

{
"token": "refresh-token-to-revoke"
}

Service authentication

For server-to-server communication, Chive uses service auth JWTs:

POST /xrpc/pub.chive.sync.refreshRecord
Authorization: Bearer eyJhbGciOiJFUzI1NiIs...
X-Service-DID: did:plc:chive-service

Service auth JWT structure

{
"iss": "did:plc:chive-service",
"aud": "did:web:api.chive.pub",
"exp": 1704413100,
"iat": 1704412800,
"lxm": "pub.chive.sync.refreshRecord"
}
ClaimDescription
issService DID (issuer)
audTarget service DID
lxmLexicon method being called

Service auth tokens are short-lived (5 minutes) and scoped to specific operations.

Authorization

After authentication, Chive checks authorization using role-based access control (RBAC).

Roles

RoleDescription
anonymousUnauthenticated users
readerStandard read access
authorEprint submission permissions
moderatorKnowledge graph governance and proposal review
graph-editorKnowledge graph editing (node and edge proposals)
alpha-testerAccess to alpha features during testing
adminFull access to admin dashboard and all endpoints

Permission matrix

OperationAnonymousUserResearcherEditor
Read eprintsYesYesYesYes
Submit eprintNoYesYesYes
Write reviewNoYesYesYes
Create endorsementNoYesYesYes
Propose fieldNoYesYesYes
Vote on proposalNoYesYesYes
Approve authority recordNoNoNoYes
Moderate contentNoNoNoYes

Checking permissions

The API returns 403 Forbidden when authorization fails:

{
"error": "Forbidden",
"message": "You do not have permission to perform this action",
"details": {
"required_role": "trusted_editor",
"your_role": "user"
}
}

WebAuthn (passkeys)

Chive supports WebAuthn for passwordless authentication and MFA:

Register a passkey

POST /auth/webauthn/register/options
Authorization: Bearer eyJhbGciOiJFUzI1NiIs...

Response includes WebAuthn challenge and options for navigator.credentials.create().

Authenticate with passkey

POST /auth/webauthn/authenticate/options
Content-Type: application/json

{
"handle": "alice.bsky.social"
}

Multi-factor authentication

Users can enable MFA for additional security:

MFA methods

MethodDescription
TOTPTime-based one-time passwords (Google Authenticator)
WebAuthnHardware security keys or passkeys
Recovery codesBackup codes for account recovery

MFA challenge flow

When MFA is enabled, authentication requires a second step:

POST /oauth/token
Content-Type: application/json

{
"grant_type": "authorization_code",
"code": "auth-code-here",
"code_verifier": "pkce-verifier"
}

Response (MFA required):

{
"error": "mfa_required",
"mfa_token": "temporary-mfa-token",
"allowed_methods": ["totp", "webauthn"]
}

Complete MFA:

POST /oauth/mfa/verify
Content-Type: application/json

{
"mfa_token": "temporary-mfa-token",
"method": "totp",
"code": "123456"
}

Security headers

API responses include security headers:

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'
Strict-Transport-Security: max-age=31536000; includeSubDomains

Error responses

Authentication errors

ErrorHTTP StatusMeaning
AuthRequired401No authentication provided
InvalidToken401Token is malformed or invalid
ExpiredToken401Token has expired
RevokedToken401Token was revoked
InvalidCredentials401Wrong username/password

Authorization errors

ErrorHTTP StatusMeaning
Forbidden403Authenticated but not authorized
InsufficientScope403Token lacks required scope
AccountSuspended403Account is suspended

Best practices

Token storage

PlatformRecommended storage
Web browsersHttpOnly cookies or secure localStorage
Mobile appsSecure keychain/keystore
Server-sideEnvironment variables or secrets manager

Security recommendations

  1. Always use HTTPS: Never transmit tokens over unencrypted connections
  2. Validate token expiry: Check exp claim before using tokens
  3. Rotate refresh tokens: Use each refresh token only once
  4. Implement PKCE: Always use PKCE for OAuth flows
  5. Store tokens securely: Use platform-appropriate secure storage

Client implementation

TypeScript example

// web/lib/auth/oauth-client.ts
import { BrowserOAuthClient } from '@atproto/oauth-client-browser';
import { Agent } from '@atproto/api';

// Initialize OAuth client
const client = await BrowserOAuthClient.load({
clientId: 'https://chive.pub/oauth/client-metadata.json',
handleResolver,
});

// Start login flow
const loginUrl = await client.authorize('alice.bsky.social', {
scope: 'atproto transition:generic',
});
window.location.href = loginUrl.toString();

// Handle callback (on return from auth server)
const result = await client.callback(new URLSearchParams(window.location.search));
const session = result.session;
const agent = new Agent(session);

// Make authenticated request using service auth
// web/lib/api/client.ts
import { authApi } from '@/lib/api/client';

// authApi automatically adds service auth JWT via middleware
const { data } = await authApi.GET('/xrpc/pub.chive.actor.getMyProfile');

The authApi client handles authentication automatically:

  1. Gets the authenticated Agent from OAuth session
  2. Calls com.atproto.server.getServiceAuth to get a service auth JWT
  3. Adds the JWT to the Authorization header

Next steps