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.
// The access token is automatically managed by @cloudillo/core
import*ascloudillofrom'@cloudillo/core'consttoken=awaitcloudillo.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
constapi=cloudillo.createApiClient()
constaction=awaitapi.actions.create({
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:
constapi=cloudillo.createApiClient()
// Get a proxy token for accessing a remote instance
constproxyToken=awaitapi.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*ascloudillofrom'@cloudillo/core'// The init() function receives the token from the shell via postMessage
consttoken=awaitcloudillo.init('my-app')
// All API calls now use this token automatically
constapi=cloudillo.createApiClient()
constprofile=awaitapi.profiles.getOwn() // Authenticated request
For Standalone Apps
For standalone applications, you need to handle authentication manually:
import*ascloudillofrom'@cloudillo/core'awaitcloudillo.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
constcanModerate=cloudillo.roles?.includes('admin') ||cloudillo.roles?.includes('moderator')
Requesting Specific Roles
// Request an access token with specific roles
constresponse=awaitfetch(
'/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/core)
// ✅ cloudillo.accessToken = token
// Or use httpOnly cookies (server-side)
import { FetchError } from'@cloudillo/core'try {
constapi=cloudillo.createApiClient()
constdata=awaitapi.profiles.getOwn()
} catch (error) {
if (errorinstanceofFetchError) {
if (error.code==='E-AUTH-UNAUTH') {
// Unauthorized - token expired or invalid
window.location.href='/login' } elseif (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
constapi=cloudillo.createApiClient({
baseUrl:'https://api.cloudillo.com'})
// ❌ Bad (only for local development)
constapi=cloudillo.createApiClient({
baseUrl:'http://localhost:3000'})
WebAuthn Support
Cloudillo supports WebAuthn for passwordless authentication.
Registration Flow
// 1. Get registration options from server
constoptionsResponse=awaitfetch('/auth/webauthn/register/options', {
method:'POST',
headers: { 'Content-Type':'application/json' },
body: JSON.stringify({ idTag:'alice@example.com' })
})
constoptions=awaitoptionsResponse.json()
// 2. Create credential with browser WebAuthn API
constcredential=awaitnavigator.credentials.create({
publicKey: options})
// 3. Verify credential with server
constverifyResponse=awaitfetch('/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(newUint8Array(credential.rawId)),
response: {
clientDataJSON: Array.from(newUint8Array(credential.response.clientDataJSON)),
attestationObject: Array.from(newUint8Array(credential.response.attestationObject))
},
type:credential.type }
})
})
// 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
// Check token expiration and refresh
asyncfunctionensureAuthenticated() {constapi=cloudillo.createApiClient()
try {
// Try to use the current token
awaitapi.profiles.getOwn()
} catch (error) {
if (error.code==='E-AUTH-UNAUTH') {
// Token expired, get a new one
const { token } =awaitapi.auth.loginToken.get()
cloudillo.accessToken=token }
}
}