Authentication
Authentication
Cloudillo uses JWT-based authentication with three types of tokens for different use cases. This guide explains how authentication works and how to use it in your applications.
Token Types
1. Access Token (Session Token)
The access token is your primary authentication credential for API requests.
Characteristics:
- JWT signed with ES384 elliptic curve algorithm
- Contains:
tnId(tenant ID),idTag(user identity),roles,iat(issued at),exp(expiration) - Used for all authenticated API requests
- Typically valid for the duration of a session
Usage:
// The access token is automatically managed by @cloudillo/base
import * as cloudillo from '@cloudillo/base'
const token = await cloudillo.init('my-app')
// Token is now stored and used automatically for all API calls
// Or access it directly
console.log(cloudillo.accessToken)2. Action Token (Federation Token)
Action tokens are cryptographically signed events used for federation between Cloudillo instances.
Characteristics:
- Represents a specific action (POST, CMNT, REACT, etc.)
- Signed by the issuer’s private key
- Can be verified by anyone with the issuer’s public key
- Enables trust-free federation
Usage:
// Action tokens are created automatically when you post actions
const api = cloudillo.createApiClient()
const action = await api.action.post({
type: 'POST',
content: { text: 'Hello, world!' }
})
// The server automatically signs the action with your key
// Other instances can verify it without trusting your server
3. Proxy Token (Cross-Instance Token)
Proxy tokens enable accessing resources on remote Cloudillo instances.
Characteristics:
- Short-lived (typically 5 minutes)
- Grants read access to specific resources
- Used for federation scenarios
Usage:
const api = cloudillo.createApiClient()
// Get a proxy token for accessing a remote instance
const proxyToken = await api.auth.proxyToken.get()
// Use it to fetch resources from another instance
// (typically handled automatically by the client)
Authentication Flow
For Microfrontend Apps
When running inside the Cloudillo shell, authentication is handled automatically:
import * as cloudillo from '@cloudillo/base'
// The init() function receives the token from the shell via postMessage
const token = await cloudillo.init('my-app')
// All API calls now use this token automatically
const api = cloudillo.createApiClient()
const profile = await api.me.get() // Authenticated request
For Standalone Apps
For standalone applications, you need to handle authentication manually:
import * as cloudillo from '@cloudillo/base'
// Option 1: Manual token management
cloudillo.accessToken = 'your-jwt-token-here'
const api = cloudillo.createApiClient()
// Option 2: Login flow
const response = await fetch('https://your-server.com/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
idTag: 'alice@example.com',
password: 'secret123'
})
})
const { token, tnId, idTag, name, roles } = await response.json()
cloudillo.accessToken = token
cloudillo.tnId = tnId
cloudillo.idTag = idTag
cloudillo.roles = rolesAuthentication Endpoints
POST /auth/register
Register a new user account.
Request:
{
"idTag": "alice@example.com",
"password": "secure-password",
"name": "Alice Johnson",
"profilePic": "https://example.com/alice.jpg"
}Response:
{
"data": {
"tnId": 12345,
"idTag": "alice@example.com",
"name": "Alice Johnson",
"token": "eyJhbGc..."
}
}POST /auth/login
Authenticate and receive an access token.
Request:
{
"idTag": "alice@example.com",
"password": "secure-password"
}Response:
{
"data": {
"tnId": 12345,
"idTag": "alice@example.com",
"name": "Alice Johnson",
"profilePic": "/file/b1~abcd1234",
"roles": ["user", "admin"],
"token": "eyJhbGc...",
"settings": [
["theme", "dark"],
["language", "en"]
]
}
}POST /auth/logout
Invalidate the current session.
Request:
POST /auth/logout
Authorization: Bearer eyJhbGc...Response:
{
"data": "ok"
}GET /auth/access-token
Exchange credentials for a scoped access token.
Query Parameters:
idTag- User identitypassword- User passwordroles- Optional: Requested roles (comma-separated)ttl- Optional: Token lifetime in seconds
Response:
{
"data": {
"token": "eyJhbGc...",
"expiresAt": 1735555555
}
}GET /auth/proxy-token
Get a proxy token for accessing remote resources.
Request:
GET /auth/proxy-token?target=bob@remote.com
Authorization: Bearer eyJhbGc...Response:
{
"data": {
"token": "eyJhbGc...",
"expiresAt": 1735555555
}
}Role-Based Access Control
Cloudillo supports role-based access control (RBAC) for fine-grained permissions.
Default Roles
- user - Standard user permissions (read/write own data)
- admin - Administrative permissions (manage server, users)
- read - Read-only access
- write - Write access to resources
Checking Roles
import * as cloudillo from '@cloudillo/base'
await cloudillo.init('my-app')
// Check if user has a specific role
if (cloudillo.roles?.includes('admin')) {
console.log('User is an admin')
}
// Enable/disable features based on roles
const canModerate = cloudillo.roles?.includes('admin') ||
cloudillo.roles?.includes('moderator')Requesting Specific Roles
// Request an access token with specific roles
const response = await fetch(
'/auth/access-token?idTag=alice@example.com&password=secret&roles=user,admin'
)Token Validation
All tokens are validated on the server for:
- Signature verification - Using ES384 algorithm
- Expiration check - Tokens expire after a set period
- Tenant isolation - Tokens are tied to specific tenants
- Role validation - Roles must be granted by the server
Security Best Practices
1. Token Storage
For web apps:
// Don't store tokens in localStorage (XSS vulnerable)
// ❌ localStorage.setItem('token', token)
// Use memory storage (managed by @cloudillo/base)
// ✅ cloudillo.accessToken = token
// Or use httpOnly cookies (server-side)
2. Token Renewal
// Implement token renewal before expiration
async function renewToken() {
const api = cloudillo.createApiClient()
try {
const newToken = await api.auth.loginToken.get()
cloudillo.accessToken = newToken.token
} catch (error) {
// Token expired, redirect to login
window.location.href = '/login'
}
}
// Renew every 50 minutes (if token lasts 60 minutes)
setInterval(renewToken, 50 * 60 * 1000)3. Error Handling
import { FetchError } from '@cloudillo/base'
try {
const api = cloudillo.createApiClient()
const data = await api.me.get()
} catch (error) {
if (error instanceof FetchError) {
if (error.code === 'E-AUTH-UNAUTH') {
// Unauthorized - token expired or invalid
window.location.href = '/login'
} else if (error.code === 'E-AUTH-FORBID') {
// Forbidden - insufficient permissions
alert('You do not have permission to access this resource')
}
}
}4. HTTPS Only
Always use HTTPS in production:
// ✅ Good
const api = cloudillo.createApiClient({
baseUrl: 'https://api.cloudillo.com'
})
// ❌ Bad (only for local development)
const api = cloudillo.createApiClient({
baseUrl: 'http://localhost:3000'
})WebAuthn Support
Cloudillo supports WebAuthn for passwordless authentication.
Registration Flow
// 1. Get registration options from server
const optionsResponse = await fetch('/auth/webauthn/register/options', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ idTag: 'alice@example.com' })
})
const options = await optionsResponse.json()
// 2. Create credential with browser WebAuthn API
const credential = await navigator.credentials.create({
publicKey: options
})
// 3. Verify credential with server
const verifyResponse = await fetch('/auth/webauthn/register/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
idTag: 'alice@example.com',
credential: {
id: credential.id,
rawId: Array.from(new Uint8Array(credential.rawId)),
response: {
clientDataJSON: Array.from(new Uint8Array(credential.response.clientDataJSON)),
attestationObject: Array.from(new Uint8Array(credential.response.attestationObject))
},
type: credential.type
}
})
})Authentication Flow
// 1. Get authentication options
const optionsResponse = await fetch('/auth/webauthn/login/options', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ idTag: 'alice@example.com' })
})
const options = await optionsResponse.json()
// 2. Get assertion with browser WebAuthn API
const assertion = await navigator.credentials.get({
publicKey: options
})
// 3. Verify assertion with server
const verifyResponse = await fetch('/auth/webauthn/login/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
idTag: 'alice@example.com',
credential: {
id: assertion.id,
rawId: Array.from(new Uint8Array(assertion.rawId)),
response: {
clientDataJSON: Array.from(new Uint8Array(assertion.response.clientDataJSON)),
authenticatorData: Array.from(new Uint8Array(assertion.response.authenticatorData)),
signature: Array.from(new Uint8Array(assertion.response.signature)),
userHandle: assertion.response.userHandle ?
Array.from(new Uint8Array(assertion.response.userHandle)) : null
},
type: assertion.type
}
})
})
const { token } = await verifyResponse.json()
cloudillo.accessToken = tokenMulti-Tenant Considerations
Every request in Cloudillo is scoped to a tenant:
// The tnId is automatically included in all requests
console.log('Tenant ID:', cloudillo.tnId)
// Tokens are tenant-specific and cannot access other tenants' data
// This is enforced at the database level for security
Common Authentication Scenarios
Scenario 1: User Login
async function login(idTag: string, password: string) {
const response = await fetch('/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ idTag, password })
})
if (!response.ok) {
throw new Error('Login failed')
}
const data = await response.json()
cloudillo.accessToken = data.data.token
cloudillo.tnId = data.data.tnId
cloudillo.idTag = data.data.idTag
cloudillo.roles = data.data.roles
return data.data
}Scenario 2: Automatic Token Management
// @cloudillo/react handles this automatically
import { CloudilloProvider } from '@cloudillo/react'
function App() {
return (
<CloudilloProvider appName="my-app">
{/* Token is managed automatically */}
<YourApp />
</CloudilloProvider>
)
}Scenario 3: Token Refresh
// Check token expiration and refresh
async function ensureAuthenticated() {
const api = cloudillo.createApiClient()
try {
// Try to use the current token
await api.me.get()
} catch (error) {
if (error.code === 'E-AUTH-UNAUTH') {
// Token expired, get a new one
const { token } = await api.auth.loginToken.get()
cloudillo.accessToken = token
}
}
}Next Steps
- Client Libraries - Learn about available libraries
- REST API - Explore all API endpoints
- Error Handling - Handle authentication errors
- Microfrontends - Build shell-integrated apps