Skip to main content

Authentication

AFFiNE supports multiple authentication methods to access the API. Choose the method that best fits your use case.

Authentication Methods

AFFiNE provides three authentication approaches:
  1. Session Cookies - For web applications and browsers
  2. Access Tokens - For API integrations and automation
  3. OAuth 2.0 - For third-party applications

Session-Based Authentication

Session authentication uses HTTP cookies and is primarily designed for web applications.

Sign In with Password

Authenticate using email and password:
curl -X POST https://your-affine-instance.com/api/auth/sign-in \
  -H "Content-Type: application/json" \
  -c cookies.txt \
  -d '{
    "email": "user@example.com",
    "password": "your-password"
  }'
Request a sign-in link sent to the user’s email:
  1. Request Magic Link
    curl -X POST https://your-affine-instance.com/api/auth/sign-in \
      -H "Content-Type: application/json" \
      -d '{
        "email": "user@example.com",
        "callbackUrl": "/magic-link"
      }'
    
  2. User receives email with a magic link containing a one-time token
  3. Exchange token for session
    curl -X POST https://your-affine-instance.com/api/auth/magic-link \
      -H "Content-Type: application/json" \
      -c cookies.txt \
      -d '{
        "email": "user@example.com",
        "token": "OTP_FROM_EMAIL"
      }'
    

Session Cookies

Successful authentication sets these cookies:
  • affine_session - Session identifier (HttpOnly, Secure)
  • affine_user_id - Current user ID (client-readable)
  • affine_csrf_token - CSRF protection token
The affine_session cookie is HttpOnly and Secure. It cannot be accessed via JavaScript and is only sent over HTTPS in production.

Check Current Session

Verify the current authenticated user:
cURL
curl -X GET https://your-affine-instance.com/api/auth/session \
  -b cookies.txt
JavaScript
const response = await fetch('https://your-affine-instance.com/api/auth/session', {
  credentials: 'include'
});

const { user } = await response.json();
console.log('Current user:', user);
Response:
{
  "user": {
    "id": "clx1234567890",
    "email": "user@example.com",
    "name": "John Doe",
    "avatarUrl": "https://...",
    "emailVerified": true,
    "hasPassword": true
  }
}

Access Token Authentication

Access tokens are ideal for API integrations, scripts, and automation tools.

Generate Access Token

Create a new access token via GraphQL (requires existing authentication):
curl -X POST https://your-affine-instance.com/graphql \
  -H "Content-Type: application/json" \
  -b cookies.txt \
  -d '{
    "query": "mutation { generateUserAccessToken(input: { name: \"My API Token\", expiresAt: \"2025-12-31T23:59:59Z\" }) { token expiresAt } }"
  }'
name
string
required
A descriptive name for the token (e.g., “CI/CD Pipeline”, “Mobile App”)
expiresAt
DateTime
Optional expiration date. If not provided, the token never expires.
Security Best Practices:
  • Store tokens securely (use environment variables or secret managers)
  • Set reasonable expiration dates
  • Revoke tokens when no longer needed
  • Use different tokens for different applications

Using Access Tokens

Include the token in the Authorization header with the Bearer scheme:
curl -X POST https://your-affine-instance.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer affine_sk_xxxxxxxxxxxxxxxx" \
  -d '{
    "query": "query { currentUser { id email name } }"
  }'

List Access Tokens

View all active tokens for the current user:
query {
  currentUser {
    revealedAccessTokens {
      id
      name
      createdAt
      expiresAt
    }
  }
}
The token field is only revealed when the token is first created. After that, only the token metadata is available.

Revoke Access Token

Delete a token to immediately invalidate it:
mutation {
  revokeUserAccessToken(id: "token_id_here")
}

OAuth 2.0 Authentication

AFFiNE supports OAuth 2.0 for third-party application integrations.

Supported Providers

  • Google - Sign in with Google account
  • GitHub - Sign in with GitHub account
  • Apple - Sign in with Apple ID
  • OIDC - Custom OpenID Connect providers

OAuth Sign-In Flow

  1. Redirect user to OAuth provider
    GET /api/oauth/authorize?provider=google
    
  2. User authorizes on provider’s site
  3. Provider redirects back with code
    GET /api/oauth/callback?code=xxx&state=yyy
    
  4. Session is established - User is now authenticated

Initiate OAuth Flow

JavaScript
// Redirect user to OAuth authorization
window.location.href = 'https://your-affine-instance.com/api/oauth/authorize?provider=google';
Connect an OAuth provider to an existing account:
mutation {
  linkCalendarAccount(input: {
    provider: Google
    redirectUri: "https://your-app.com/oauth/callback"
  })
}

Authentication Errors

Handle these common authentication errors:
Error CodeDescriptionResolution
AUTHENTICATION_REQUIREDNo valid authentication providedSign in or provide access token
WRONG_SIGN_IN_CREDENTIALSInvalid email/passwordCheck credentials
INVALID_EMAIL_TOKENMagic link expired or invalidRequest a new magic link
EMAIL_VERIFICATION_REQUIREDEmail not verifiedVerify email address
UNSUPPORTED_CLIENT_VERSIONClient version too oldUpdate client version
USER_NOT_FOUNDUser account doesn’t existCreate account or check email

Example Error Response

{
  "errors": [
    {
      "message": "Authentication required",
      "extensions": {
        "code": "AUTHENTICATION_REQUIRED",
        "status": 401
      }
    }
  ],
  "data": null
}

Security Considerations

CSRF Protection

For session-based authentication, CSRF protection is automatically enforced:
  • The affine_csrf_token cookie must be sent as a header for state-changing operations
  • Header name: x-affine-csrf-token
const csrfToken = document.cookie
  .split('; ')
  .find(row => row.startsWith('affine_csrf_token='))
  ?.split('=')[1];

fetch('/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-affine-csrf-token': csrfToken
  },
  credentials: 'include',
  body: JSON.stringify({ query: '...' })
});

HTTPS Required

All authentication operations must use HTTPS in production:
Never send credentials over unencrypted HTTP connections. All cookies are marked Secure in production.

Token Storage

Best practices for storing access tokens: Do:
  • Store in environment variables
  • Use secret management services (AWS Secrets Manager, HashiCorp Vault)
  • Encrypt at rest
  • Rotate periodically
Don’t:
  • Hardcode in source code
  • Commit to version control
  • Store in client-side JavaScript
  • Share across applications

Multi-Factor Authentication

AFFiNE supports email-based verification:
mutation {
  sendVerifyEmail(callbackUrl: "https://your-app.com/verify")
}
Verify the email token:
mutation {
  verifyEmail(token: "verification_token_here")
}

Session Management

Session Duration

  • Default TTL: Sessions are long-lived and refresh automatically
  • Refresh: Sessions are refreshed on each authenticated request
  • Expiration: Sessions expire after extended inactivity

Multiple Sessions

AFFiNE supports multiple concurrent sessions:
GET /api/auth/sessions
Response:
{
  "users": [
    {
      "id": "user1",
      "email": "user1@example.com",
      "name": "User One"
    },
    {
      "id": "user2",
      "email": "user2@example.com",
      "name": "User Two"
    }
  ]
}

Sign Out

Terminate the current session:
cURL
curl -X POST https://your-affine-instance.com/api/auth/sign-out \
  -H "x-affine-csrf-token: CSRF_TOKEN" \
  -b cookies.txt
JavaScript
const csrfToken = document.cookie
  .split('; ')
  .find(row => row.startsWith('affine_csrf_token='))
  ?.split('=')[1];

await fetch('https://your-affine-instance.com/api/auth/sign-out', {
  method: 'POST',
  headers: {
    'x-affine-csrf-token': csrfToken
  },
  credentials: 'include'
});

Sign Out Specific User

Remove a specific user from a multi-user session:
POST /api/auth/sign-out?user_id=USER_ID

Testing Authentication

Verify your authentication setup with these queries:

Check Authentication Status

query {
  currentUser {
    id
    email
    name
    emailVerified
    hasPassword
    disabled
  }
}

Verify Token Permissions

query {
  workspaces {
    id
    name
    public
  }
}
If authentication is successful, you’ll receive workspace data. Otherwise, you’ll get an AUTHENTICATION_REQUIRED error.

Next Steps

  1. Choose your authentication method based on your use case
  2. Implement authentication in your application
  3. Test with simple queries to verify authentication works
  4. Explore the API and build your integration

Additional Resources

  • GraphQL Schema: See available auth mutations in the schema
  • Security Guide: Best practices for secure API usage
  • Rate Limits: Understand API rate limiting