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 with user's PDS
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 for user authentication:

┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│ Client │ │ Chive │ │ User PDS │ │ User │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
│ 1. Login │ │ │
│───────────────►│ │ │
│ │ │ │
│ 2. Redirect │ │ │
│◄───────────────│ │ │
│ │ │ │
│ 3. Auth request │ │
│────────────────────────────────►│ │
│ │ │ │
│ │ │ 4. Consent │
│ │ │◄───────────────│
│ │ │ │
│ 5. Auth code │ │ │
│◄────────────────────────────────│ │
│ │ │ │
│ 6. Exchange │ │ │
│───────────────►│ │ │
│ │ │ │
│ 7. Tokens │ │ │
│◄───────────────│ │ │
└────────────────┴────────────────┴────────────────┘

Step 1: Initiate login

GET /oauth/authorize?
handle=alice.bsky.social&
redirect_uri=https://yourapp.com/callback&
state=random-state-value

Step 2: User redirected to PDS

Chive resolves the user's DID and redirects to their PDS's authorization endpoint with PKCE parameters.

The user authenticates with their PDS and grants Chive permission to access their data.

Step 4: Authorization code returned

GET https://yourapp.com/callback?
code=auth-code-here&
state=random-state-value

Step 5: Exchange code for tokens

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

{
"grant_type": "authorization_code",
"code": "auth-code-here",
"redirect_uri": "https://yourapp.com/callback",
"code_verifier": "pkce-verifier"
}

Step 6: Receive tokens

{
"access_token": "eyJhbGciOiJFUzI1NiIs...",
"refresh_token": "eyJhbGciOiJFUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"did": "did:plc:abc123...",
"handle": "alice.bsky.social"
}

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
userAuthenticated users
researcherVerified researchers
trusted_editorCommunity moderators
authority_editorLibrary science experts
adminSystem administrators

Permission matrix

OperationAnonymousUserResearcherEditor
Read preprintsYesYesYesYes
Submit preprintNoYesYesYes
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

import { ChiveAuth } from '@chive/auth-client';

const auth = new ChiveAuth({
clientId: 'your-client-id',
redirectUri: 'https://yourapp.com/callback'
});

// Start login flow
const loginUrl = await auth.getLoginUrl({
handle: 'alice.bsky.social'
});

// Handle callback
const tokens = await auth.handleCallback(callbackUrl);

// Make authenticated request
const response = await fetch('/xrpc/pub.chive.actor.getMyProfile', {
headers: {
Authorization: `Bearer ${tokens.accessToken}`
}
});

// Refresh token
const newTokens = await auth.refreshToken(tokens.refreshToken);

Next steps