Skip to main content

API Authentication

SDD Classification: L3-Technical Authority: Engineering Team Review Cycle: Quarterly
Materi uses JWT (JSON Web Tokens) for secure, stateless authentication. This guide covers all authentication methods, token management, and security best practices.

Authentication Flow Overview


JWT Token Structure

Access Token Claims

{
  "user_id": "2441f8c8-0e14-4a71-8f32-8cbbf80382ae",
  "email": "[email protected]",
  "name": "John Doe",
  "workspace_ids": ["ws_123", "ws_456"],
  "roles": ["member", "admin"],
  "permissions": {
    "documents": ["read", "write"],
    "workspaces": ["read"]
  },
  "iat": 1704067200,
  "exp": 1704068100,
  "jti": "token_unique_id",
  "token_type": "access"
}

Token Properties

PropertyDescriptionValue
AlgorithmSigning algorithmRS256
Key SizeRSA key size2048-bit minimum
Access TTLAccess token expiry15 minutes
Refresh TTLRefresh token expiry30 days
Collaboration TTLWebSocket session token60 minutes

Authentication Methods

Method 1: Email/Password Login

POST /auth/login
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "secure_password"
}
Response:
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 900,
  "user": {
    "id": "2441f8c8-0e14-4a71-8f32-8cbbf80382ae",
    "email": "[email protected]",
    "name": "John Doe"
  }
}

Method 2: OAuth 2.0 Authorization Code

Step 1: Redirect to Authorization
const authUrl = `https://api.materi.dev/oauth/authorize?
  client_id=${CLIENT_ID}&
  response_type=code&
  scope=documents:read documents:write workspaces:read&
  redirect_uri=${encodeURIComponent(CALLBACK_URL)}&
  state=${generateState()}`;

window.location.href = authUrl;
Step 2: Exchange Code for Tokens
POST /oauth/token
Content-Type: application/json

{
  "grant_type": "authorization_code",
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "code": "authorization_code_from_callback",
  "redirect_uri": "https://yourapp.com/callback"
}

Method 3: Client Credentials (Server-to-Server)

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

{
  "grant_type": "client_credentials",
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "scope": "documents:read workspaces:read"
}

OAuth Scopes

ScopeDescriptionAccess Level
documents:readRead document contentRead-only
documents:writeCreate and edit documentsRead/Write
documents:deleteDelete documentsFull
workspaces:readList workspaces and membersRead-only
workspaces:writeManage workspace settingsRead/Write
users:readRead user profilesRead-only
users:writeUpdate user profilesRead/Write
ai:generateUse AI generation featuresExecute
collaborateReal-time collaborationWebSocket

Token Refresh

Access tokens expire after 15 minutes. Use refresh tokens to obtain new access tokens:
POST /auth/refresh
Content-Type: application/json

{
  "refresh_token": "eyJhbGciOiJSUzI1NiIs..."
}
Response:
{
  "access_token": "new_access_token",
  "refresh_token": "new_refresh_token",
  "token_type": "Bearer",
  "expires_in": 900
}
Refresh tokens are rotated on each use. The old refresh token becomes invalid immediately. Store the new refresh token securely.

Automatic Token Refresh Pattern

class TokenManager {
  constructor() {
    this.accessToken = null;
    this.refreshToken = null;
    this.refreshPromise = null;
  }

  async getValidToken() {
    if (this.isTokenValid(this.accessToken)) {
      return this.accessToken;
    }

    // Avoid concurrent refresh requests
    if (this.refreshPromise) {
      return this.refreshPromise;
    }

    this.refreshPromise = this.refreshAccessToken();
    try {
      this.accessToken = await this.refreshPromise;
      return this.accessToken;
    } finally {
      this.refreshPromise = null;
    }
  }

  async refreshAccessToken() {
    const response = await fetch('https://api.materi.dev/v1/auth/refresh', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refresh_token: this.refreshToken })
    });

    if (!response.ok) {
      throw new Error('Token refresh failed');
    }

    const data = await response.json();
    this.refreshToken = data.refresh_token;
    return data.access_token;
  }

  isTokenValid(token) {
    if (!token) return false;
    const payload = JSON.parse(atob(token.split('.')[1]));
    return payload.exp * 1000 > Date.now() + 60000; // 1 min buffer
  }
}

Token Revocation

Revoke Current Token

POST /auth/revoke
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "refresh_token": "token_to_revoke"
}

Revoke All User Sessions

POST /auth/revoke-all
Authorization: Bearer <access_token>

SSO Integration

Supported Providers

ProviderProtocolEnterprise
GoogleOAuth 2.0Yes
GitHubOAuth 2.0Yes
MicrosoftOAuth 2.0 / SAMLYes
OktaSAML 2.0Yes
Azure ADSAML 2.0Yes
OneLoginSAML 2.0Yes

SAML 2.0 Configuration

SAML Service Provider Metadata:
PropertyValue
Entity IDhttps://api.materi.dev/saml/metadata
ACS URLhttps://api.materi.dev/auth/saml/acs
SLO URLhttps://api.materi.dev/auth/saml/slo

Security Best Practices

Token Storage Recommendations

PlatformRecommended Storage
BrowserHttpOnly cookies or secure memory
MobileSecure keychain/keystore
ServerEnvironment variables or secrets manager
Never store tokens in:
  • LocalStorage (XSS vulnerable)
  • SessionStorage (XSS vulnerable)
  • URL parameters
  • Log files

Rate Limiting for Auth Endpoints

EndpointLimitWindow
/auth/login5 attempts15 minutes per IP
/auth/register3 accounts1 hour per IP
/auth/password-reset3 requests1 hour per IP
/auth/refresh10 requests1 minute

Public Key Endpoint

Validate JWT tokens locally using the public key:
GET /.well-known/jwks.json
Response:
{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "key_id_2024",
      "alg": "RS256",
      "n": "base64url_encoded_modulus",
      "e": "AQAB"
    }
  ]
}
Key Rotation Policy:
  • Keys rotate every 90 days
  • Dual-key overlap for 7 days
  • Cache JWKS for maximum 5 minutes

Authentication Errors

Error CodeHTTP StatusDescription
INVALID_CREDENTIALS401Email or password incorrect
INVALID_TOKEN401Token malformed or expired
TOKEN_REVOKED401Token has been revoked
REFRESH_TOKEN_EXPIRED401Refresh token expired
ACCOUNT_LOCKED403Too many failed attempts
ACCOUNT_SUSPENDED403Account disabled by admin
INSUFFICIENT_SCOPE403Token lacks required scope


Document Status: Complete Version: 2.0