Skip to main content

Auth Block

Auth Block

Enterprise-grade authentication for modern applications

The Auth Block is a complete authentication and authorization service that provides secure user management, JWT token handling, multi-factor authentication, passwordless login, AI agent identity, service tokens, OIDC provider capabilities, and multi-tenant support. It uses RS256 JWT tokens with company-specific RSA key pairs and follows the JSON:API specification.

Key Features

  • RS256 JWT Authentication - Asymmetric encryption with company-level RSA keys
  • Multi-Factor Authentication - Two channels: email OTP (zero-friction) and TOTP (authenticator apps) with backup codes
  • Passwordless Login via Email OTP - 6-digit OTP with anti-enumeration and MFA gate
  • OIDC Provider - Discovery, JWKS, and token endpoint with 4 grant types including agent-identity
  • AI Agent Identity - Agent registration, Ed25519 signature verification, and OAuth token exchange
  • Service Tokens (M2M) - Agent tokens (24h) and service tokens (90 days) with scope validation
  • reCAPTCHA Bot Protection - Per-tenant inline enforcement with kill switch
  • Magic Link Authentication - Create, validate, exchange, and send passwordless login links
  • API Key Management - Service-to-service authentication with AWS Gateway integration
  • Multi-tenant Architecture - Complete data isolation per tenant using PostgreSQL schemas
  • OAuth 2.0 Refresh Tokens - Optional long-lived session support
  • Role-Based Access Control - Fine-grained permissions system
  • Block Permissions Provisioning - HMAC-authenticated internal scope registry

API Endpoint

ServiceURL
Authhttps://auth.api.us.23blocks.com

Environment Routing: Use the same URL for all environments. Your API key determines the environment:

  • pk_test_* / sk_test_* → Routes to Staging
  • pk_live_* / sk_live_* → Routes to Production

Quick Start

Installation

npm install @23blocks/sdk

Basic Authentication

import { create23BlocksClient } from '@23blocks/sdk';

const client = create23BlocksClient({
urls: { authentication: 'https://auth.api.us.23blocks.com' },
apiKey: 'your-api-key', // Use pk_test_* for staging, pk_live_* for production
});

// Register a new user
const user = await client.auth.register({
email: 'user@example.com',
password: 'SecurePassword123',
name: 'John Doe',
confirmSuccessUrl: 'https://yourapp.com/confirmed'
});

// Login
const session = await client.auth.login({
email: 'user@example.com',
password: 'SecurePassword123'
});

// Access token is available
console.log(session.accessToken);

Authentication

Required Headers

All authenticated requests require:

Authorization: Bearer [JWT_TOKEN]
X-API-Key: [COMPANY_API_KEY]
Content-Type: application/json

JWT Token Structure

Tokens are signed using RS256 with company-specific RSA keys. You can verify tokens using the public JWKS endpoint:

GET /:company_url_id/.well-known/jwks.json

API Reference

User Registration

curl -X POST https://auth.api.us.23blocks.com/auth \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"user": {
"email": "newuser@example.com",
"password": "SecurePassword123",
"password_confirmation": "SecurePassword123",
"name": "Jane Smith",
"confirm_success_url": "https://yourapp.com/email-confirmed"
}
}'

Response:

{
"data": {
"id": "usr_def456",
"type": "user",
"attributes": {
"unique_id": "usr_def456",
"email": "newuser@example.com",
"name": "Jane Smith",
"confirmed": false,
"confirmation_sent_at": "2024-01-15T10:30:00Z"
}
},
"meta": {
"message": "A confirmation email has been sent to newuser@example.com"
}
}

Sign In

curl -X POST https://auth.api.us.23blocks.com/auth/sign_in \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"email": "user@example.com",
"password": "SecurePassword123"
}'

Response:

{
"data": {
"id": "usr_abc123",
"type": "user",
"attributes": {
"unique_id": "usr_abc123",
"email": "user@example.com",
"name": "John Doe",
"confirmed": true,
"role_id": 5,
"mfa_enabled": false,
"mfa_channel": null
}
},
"meta": {
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 86400
}
}

If the user has MFA enabled, the sign-in response will include an mfa_required flag. Send the MFA code in a follow-up request.

Token Validation

curl -X GET https://auth.api.us.23blocks.com/auth/validate_token \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key"

Password Reset

Request reset:

curl -X POST https://auth.api.us.23blocks.com/auth/password \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"email": "user@example.com",
"redirect_url": "https://app.example.com/reset-password"
}'

Update password:

curl -X PUT https://auth.api.us.23blocks.com/auth/password \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"password": "NewSecurePassword123",
"password_confirmation": "NewSecurePassword123",
"reset_password_token": "abc123resettoken"
}'

Passwordless Login via Email OTP

Send a 6-digit one-time password to the user's email for passwordless authentication. Anti-enumeration protection ensures the endpoint always returns 200 regardless of whether the email exists.

Request OTP

curl -X POST https://auth.api.us.23blocks.com/auth/passwordless/request \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"email": "user@example.com"
}'

Response (always 200 for anti-enumeration):

{
"meta": {
"message": "If the email exists, a verification code has been sent."
}
}

Verify OTP

curl -X POST https://auth.api.us.23blocks.com/auth/passwordless/verify \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"email": "user@example.com",
"otp_code": "123456"
}'

Response:

{
"data": {
"id": "usr_abc123",
"type": "user",
"attributes": {
"unique_id": "usr_abc123",
"email": "user@example.com",
"name": "John Doe"
}
},
"meta": {
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 86400
}
}

OTP Rules:

  • 6-digit numeric code
  • 10-minute TTL
  • Maximum 5 verification attempts
  • MFA gate applies on verify (same as sign_in)

Multi-Factor Authentication

Auth Block supports two MFA channels:

  • Email - Zero-friction OTP sent to the user's email
  • TOTP - Time-based one-time passwords via authenticator apps (Google Authenticator, Authy, etc.)

MFA Endpoints

All MFA endpoints are under /users/:unique_id/mfa/:

EndpointMethodDescription
/mfa/setupPOSTInitialize MFA setup (returns QR code for TOTP, or sends email OTP)
/mfa/enablePOSTEnable MFA after verifying code
/mfa/disablePOSTDisable MFA (requires verification)
/mfa/verifyPOSTVerify an MFA code
/mfa/statusGETCheck MFA status and channel

Setup MFA (TOTP)

curl -X POST https://auth.api.us.23blocks.com/users/usr_abc123/mfa/setup \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key" \
-d '{"channel": "totp"}'

Response:

{
"data": {
"id": "mfa_setup_123",
"type": "mfa_setup",
"attributes": {
"channel": "totp",
"secret": "JBSWY3DPEHPK3PXP",
"qr_code_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"backup_codes": ["12345678", "87654321", "11111111", "22222222", "33333333", "44444444", "55555555", "66666666", "77777777", "88888888"]
}
}
}

Setup MFA (Email)

curl -X POST https://auth.api.us.23blocks.com/users/usr_abc123/mfa/setup \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key" \
-d '{"channel": "email"}'

Enable MFA

After verifying the setup code:

curl -X POST https://auth.api.us.23blocks.com/users/usr_abc123/mfa/enable \
-H "Authorization: Bearer your-jwt-token" \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"mfa_code": "123456"}'

Login with MFA

When MFA is enabled, sign-in and passwordless verify flows require an MFA code:

curl -X POST https://auth.api.us.23blocks.com/auth/sign_in \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"email": "user@example.com",
"password": "SecurePassword123",
"mfa_code": "123456"
}'

Backup Codes: 10 one-time backup codes are generated during setup. Each can be used once in place of an MFA code. Usage is tracked.

reCAPTCHA Bot Protection

Inline reCAPTCHA enforcement protects authentication endpoints from automated attacks. Configurable per-tenant with an instant kill switch.

How It Works

  1. Enable reCAPTCHA enforcement in your tenant configuration (auth_config['recaptcha_enforcement'])
  2. Frontend obtains a reCAPTCHA token from Google
  3. Send the token as a top-level recaptcha_token parameter with protected requests

Protected Endpoints

  • User registration (POST /auth)
  • Sign in (POST /auth/sign_in)
  • Passwordless OTP request (POST /auth/passwordless/request)
  • Password reset (POST /auth/password)
  • Password OTP (POST /auth/password/otp)

Example with reCAPTCHA

curl -X POST https://auth.api.us.23blocks.com/auth \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"recaptcha_token": "03AGdBq24PBCbwiDRaS_MJ7Z...",
"user": {
"email": "newuser@example.com",
"password": "SecurePassword123",
"password_confirmation": "SecurePassword123",
"name": "Jane Smith"
}
}'

reCAPTCHA Error Codes

CodeStatusDescription
GW-RC-001422reCAPTCHA token required (enforcement is enabled but no token sent)
GW-RC-003403reCAPTCHA verification failed (invalid or expired token)
GW-RC-004403reCAPTCHA score too low (suspected bot)

Rate Limiting

In addition to reCAPTCHA, Rack::Attack rate limiting enforces 5 registrations per IP per 5 minutes.

Kill Switch

Tenants can disable reCAPTCHA enforcement instantly by toggling the feature flag. No frontend changes required — the API will simply stop requiring the recaptcha_token parameter.

User Invitations

Invite users to join your application:

curl -X POST https://auth.api.us.23blocks.com/auth/invitation \
-H "Authorization: Bearer your-jwt-token" \
-H "Content-Type: application/json" \
-d '{
"user": {
"email": "newuser@example.com",
"name": "New User",
"role_id": 5,
"appid": "your-api-key",
"accept_invitation_url": "https://app.example.com/accept-invite"
}
}'

Magic links provide passwordless authentication via secure, time-limited tokens with delivery lifecycle tracking and audit trails.

Endpoints

EndpointMethodDescription
/companies/:id/magic_linksPOSTCreate a new magic link
/companies/:id/magic_links/:id/validateGETValidate a magic link token
/companies/:id/magic_links/:id/exchangePOSTExchange magic link for JWT
/companies/:id/magic_links/:id/send_emailPOSTSend magic link via email
/companies/:id/magic_links/:id/verify_codePOSTVerify exchange code
/companies/:id/magic_links/:idDELETEDestroy a magic link
curl -X POST https://auth.api.us.23blocks.com/companies/your-company/magic_links \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"email": "user@example.com",
"redirect_url": "https://app.example.com/dashboard",
"expires_in": 900
}'

Exchange Code Pattern

Magic links use an exchange code pattern with delivery lifecycle tracking:

  1. Create the magic link
  2. Send via email (tracks delivery status)
  3. Exchange the link token for a short-lived exchange code
  4. Verify the exchange code to receive a JWT

AI Agent Identity & Authentication

Register and authenticate AI agents with Ed25519 cryptographic signatures. Agents receive their own OAuth tokens via a dedicated grant type.

Agent Registration

curl -X POST https://auth.api.us.23blocks.com/companies/your-company/agents \
-H "Authorization: Bearer your-jwt-token" \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"agent": {
"name": "backend-processor",
"description": "Processes background tasks",
"public_key": "MCowBQYDK2VwAyEA...",
"scopes": ["files:read", "files:write", "crm:read"]
}
}'

Agent Lifecycle

EndpointMethodDescription
/companies/:id/agentsPOSTRegister a new agent
/companies/:id/agentsGETList all agents
/companies/:id/agents/:idGETGet agent details
/companies/:id/agents/:idPUTUpdate agent
/companies/:id/agents/:idDELETEDelete agent
/companies/:id/agents/:id/suspendPOSTSuspend agent
/companies/:id/agents/:id/reactivatePOSTReactivate agent

Agent OAuth Token Exchange

Agents authenticate by signing a challenge with their Ed25519 private key:

curl -X POST https://auth.api.us.23blocks.com/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "urn:aid:agent-identity",
"agent_id": "agent_abc123",
"signature": "base64-encoded-ed25519-signature",
"timestamp": "2026-05-19T12:00:00Z"
}'

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 86400,
"scope": "files:read files:write crm:read"
}

Token Introspection (RFC 7662)

Introspect both user and agent tokens:

curl -X POST https://auth.api.us.23blocks.com/oauth/introspect \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"token": "eyJhbGciOiJSUzI1NiIs..."
}'

Response:

{
"active": true,
"sub": "agent_abc123",
"client_id": "your-company",
"scope": "files:read files:write crm:read",
"token_type": "bearer",
"exp": 1716220800,
"iat": 1716134400,
"iss": "https://auth.api.us.23blocks.com/your-company"
}

Service Tokens (M2M)

Service tokens provide long-lived authentication for machine-to-machine communication. Two categories are available:

CategoryLifetimeUse Case
Agent24 hoursShort-lived agent operations
Service90 daysLong-running service integrations

Endpoints

EndpointMethodDescription
/companies/:id/service_tokensPOSTCreate a service token
/companies/:id/service_tokensGETList service tokens
/companies/:id/service_tokens/:idGETGet token details
/companies/:id/service_tokens/:idDELETERevoke a token
/companies/:id/service_tokens/:id/regeneratePOSTRegenerate a token

Create Service Token

curl -X POST https://auth.api.us.23blocks.com/companies/your-company/service_tokens \
-H "Authorization: Bearer your-jwt-token" \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"service_token": {
"name": "payment-processor",
"category": "service",
"scopes": ["wallet:read", "wallet:write"]
}
}'

Limits: Maximum 100 active service tokens per company. Usage is tracked per token.

OIDC Provider

The Auth Block acts as a full OpenID Connect provider with discovery, JWKS, and a multi-grant token endpoint. Supports both company-level and app-level OIDC.

Discovery Document

# Company-level
curl https://auth.api.us.23blocks.com/your-company/.well-known/openid-configuration

# App-level
curl https://auth.api.us.23blocks.com/your-company/apps/your-app/.well-known/openid-configuration

JWKS Endpoint

curl https://auth.api.us.23blocks.com/your-company/.well-known/jwks.json

Token Endpoint

The token endpoint supports four grant types:

Grant TypeUse Case
authorization_codeStandard OAuth flow for user login
refresh_tokenRefresh an expired access token
client_credentialsM2M service authentication
urn:aid:agent-identityAI agent authentication with Ed25519
# Client credentials example
curl -X POST https://auth.api.us.23blocks.com/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"scope": "read write"
}'

Block Permissions Provisioning

Internal API for provisioning cross-block permissions using HMAC authentication:

curl -X POST https://auth.api.us.23blocks.com/block_permissions/provision \
-H "Content-Type: application/json" \
-H "X-HMAC-Signature: sha256=..." \
-d '{
"block": "files",
"scopes": ["files:read", "files:write", "files:delete"]
}'

The internal scope registry covers all blocks: Files, Sales, Search, Rewards, Content, CRM, Forms, Wallet, and more.

Data Types

User

FieldTypeDescription
unique_idstringUnique identifier
emailstringUser's email address
namestringFull name
confirmedbooleanEmail confirmed status
role_idintegerAssigned role ID
mfa_enabledbooleanMFA status
mfa_channelstringMFA channel: "email", "totp", or null

Company

FieldTypeDescription
unique_idstringUnique identifier
url_idstringURL-safe identifier
namestringCompany name
api_access_keystringAPI key
oidc_issuerstringOpenID Connect issuer URL

Error Handling

The API uses JSON:API error format:

{
"errors": [
{
"status": "422",
"source": "Authentication Service",
"code": "10001",
"title": "Validation Error",
"detail": "Email is required and must be valid"
}
]
}

Common Error Codes

StatusCodeDescription
40010001Invalid request format
40110002Invalid authentication
401103Missing or invalid API key
40310003Insufficient permissions
403GW-RC-003reCAPTCHA verification failed
403GW-RC-004reCAPTCHA score too low
40410004Resource not found
42210005Validation failed
422GW-RC-001reCAPTCHA token required
42910006Rate limited

Rate Limiting

Endpoint TypeLimit
Authentication10 requests/minute/IP
Registration5 requests/5 minutes/IP (with reCAPTCHA)
Token validation60 requests/minute/token
General endpoints1000 requests/hour/API key

Rate limit headers in responses:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1642248000

Security Features

  • RS256 JWT - Asymmetric encryption with per-company RSA keys
  • JWKS Endpoints - Public key verification for token validation
  • Ed25519 Agent Signatures - Cryptographic agent identity verification
  • Key Rotation - Automated key rotation with graceful transitions
  • 24-hour Token Lifetime - Short-lived access tokens
  • Tenant Isolation - PostgreSQL schema separation per tenant
  • reCAPTCHA Enforcement - Bot protection with per-tenant kill switch
  • Audit Logging - Complete audit trail for security events
  • Anti-enumeration - Passwordless endpoints always return 200

SDK Examples

TypeScript/JavaScript

import { create23BlocksClient } from '@23blocks/sdk';

const client = create23BlocksClient({
urls: { authentication: 'https://auth.api.us.23blocks.com' },
apiKey: process.env.BLOCKS_API_KEY,
});

// Check if token is valid
const isValid = await client.auth.validateToken();

// Get current user
const user = await client.auth.getCurrentUser();

// Update user profile
await client.auth.updateProfile({
name: 'Updated Name',
timezone: 'America/New_York'
});

// Enable MFA
const mfaSetup = await client.auth.setupMfa();
// Show QR code to user
console.log(mfaSetup.qrCodeUrl);

// After user scans QR and enters code
await client.auth.enableMfa('123456');

React Hook Example

import { useAuth } from '@23blocks/react';

function LoginForm() {
const { login, isLoading, error } = useAuth();

const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);

await login({
email: formData.get('email'),
password: formData.get('password')
});
};

return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Signing in...' : 'Sign In'}
</button>
{error && <p>{error.message}</p>}
</form>
);
}