Access Control & Resource Sharing
Access tokens are used to authenticate and authorize requests to the API. They are usually bound to a resource, which can reside on any node within the Cloudillo network.
Token Types
Cloudillo uses different token types for different purposes:
AccessToken
Session tokens for authenticated API requests.
Purpose: Grant a client access to specific resources Format: JWT (JSON Web Token) Lifetime: 1-24 hours (configurable)
JWT Claims:
{
"sub": "alice.example.com", // Subject (user identity)
"aud": "bob.example.com", // Audience (target identity)
"exp": 1738483200, // Expiration timestamp
"iat": 1738396800, // Issued at timestamp
"scope": "resource_id" // Scope (resource identifier)
}ActionToken
Cryptographically signed tokens representing user actions (see Actions.
Purpose: Federated activity distribution Format: JWT signed with profile key (ES384) Lifetime: Permanent (immutable, content-addressed)
ProxyToken
Inter-instance authentication tokens.
Purpose: Get access tokens from a remote instance (on behalf of a user) Format: JWT Lifetime: Short-lived (1-5 minutes)
JWT Claims:
{
"sub": "alice.example.com", // Requesting identity
"aud": "bob.example.com", // Target identity
"exp": 1738400000,
"iat": 1738396800,
"scope": "resource_id" // Scope (resource identifier)
}Access Token Lifecycle
1. Token Request
Client requests an access token from their node.
2. Token Generation
The AuthAdapter creates a JWT with appropriate claims.
3. Token Validation
Every API request validates the token before processing.
4. Token Expiration
Tokens expire and must be refreshed.
Requesting an Access Token
When a user wants to access a resource, they follow this process:
- The user’s node requests an access token.
- If the resource is local, the node issues the token directly.
- If the resource is remote, the node authenticates with the remote node and requests a token on behalf of the user.
- The access token is returned to the user, allowing them to interact with the resource directly on its home node.
Security & Trust Model
- Access tokens are cryptographically signed to prevent tampering.
- Tokens have expiration times and scopes to limit misuse.
- Nodes validate access tokens before granting access to a resource.
Example 1: Request access to own resource
sequenceDiagram
box Alice frontend
participant Alice shell
participant Alice app
end
participant Alice node
Alice shell ->>+Alice node: Initiate access token request
Note right of Alice node: Create access token
Alice node ->>+Alice shell: Access token granted
deactivate Alice node
Alice shell ->>+Alice app: Open resource with this token
deactivate Alice shell
Alice app ->+Alice node: Use access token
loop Edit resource
Alice app --> Alice node: Edit resource
end
deactivate Alice app
- Alice opens a resource using her Cloudillo Shell
- Her shell initiates an access token request at her node
- Her node creates an access token and sends it to her shell
- Her shell gives the access token to the App Alice uses to open the resource
- The App uses the access token to edit the resource
Example 2: Request access to resource of an other identity
sequenceDiagram
box Alice frontend
participant Alice shell
participant Alice app
end
participant Alice node
participant Bob node
Alice shell ->>+Alice node: Initiate access token request
Note right of Alice node: Create signed request
Alice node ->>+Bob node: Request access token
Note right of Bob node: Verify signed request
Note right of Bob node: Create access token
deactivate Alice node
Bob node ->>+Alice node: Grant access token
deactivate Bob node
Alice node ->>+Alice shell: Access token granted
deactivate Alice node
Alice shell ->>+Alice app: Open resource with this token
deactivate Alice shell
Alice app ->+Bob node: Use access token
loop Edit resource
Alice app --> Bob node: Edit resource
end
deactivate Alice app
deactivate Bob node
- Alice opens a resource using her Cloudillo Shell
- Her shell initiates an access token request through her node
- Her node creates a signed request and sends it to Bob’s node
- Bob’s node creates an access token and sends it back to Alice’s node
- Alice’s node sends the access token to her shell
- Her shell gives the access token to the App Alice uses to open the resource
- The App uses the access token to edit the resource
Token Validation Process
Authentication Middleware
Cloudillo uses Axum middleware to validate tokens on protected routes:
Handler Patterns:
Pattern 1: Required Authentication
async fn protected_handler(auth: Auth) -> Result<Response> {
// auth.tn_id, auth.id_tag, auth.scope available
// Access granted only if middleware validated token
}
Pattern 2: Optional Authentication
async fn public_handler(auth: Option<Auth>) -> Result<Response> {
if let Some(auth) = auth {
// Authenticated user - access Auth context
} else {
// Anonymous access - no Auth context
}
}
The Axum extractor validates token before passing to handler.
If validation fails on required routes, request is rejected.Validation Steps
When a request includes an Authorization: Bearer <token> header:
- Extract Token: Parse JWT from Authorization header
- Decode JWT: Parse header and claims (no verification yet)
- Verify Signature: Validate using AuthAdapter-stored secret
- Check Expiration: Ensure
exp> current time - Validate Claims: Check
aud,scope,tid - Create Auth Context: Build
Authstruct for handler
pub struct Auth {
pub tn_id: TnId, // Tenant ID (database key)
pub id_tag: String, // Identity tag (e.g., "alice.example.com")
pub scope: Vec<String>, // Permissions (e.g., ["read", "write"])
pub token_type: TokenType,
}Custom Extractors
Axum extractors provide typed access to authentication context:
TnId Extractor:
struct TnId(pub i64)- Wraps internal tenant ID- Usage:
handler(TnId(tn_id): TnId)extracts from Auth context
IdTag Extractor:
struct IdTag(pub String)- Wraps user identity domain- Usage:
handler(IdTag(id_tag): IdTag)extracts from Auth context
Auth Extractor (Full Context):
tn_id: Internal tenant identifierid_tag: User identity (e.g., “alice.example.com”)scope: Permission vector (e.g., [“read”, “write”])token_type: Type of token (AccessToken, ProxyToken, etc.)
Usage: Check auth.scope.contains(&"write") for permission checks
Permission System
Cloudillo uses ABAC (Attribute-Based Access Control) for comprehensive permission management. Access tokens work in conjunction with ABAC policies to determine what actions users can perform.
Learn more: ABAC Permission System
Scope-Based Permissions
Access tokens include a scope claim that specifies permissions.
Resource-Level Permissions
Permissions are checked at multiple levels:
- File-Level: Who can access a file
- Database-Level: Who can access a database (RTDB)
- Action-Level: Who can see an action token
- API-Level: Rate limiting, quota enforcement
Permission Checking
Algorithm: Check Permission
Input: auth context, resource, required_scope
Output: Result<()>
1. Check token scope:
- If required_scope NOT in auth.scope: Return PermissionDenied
2. Load resource metadata:
- Fetch metadata by tn_id + resource_id
3. Check ownership:
- If metadata.owner == auth.id_tag: Return OK (owner has all)
4. Check sharing list:
- If auth.id_tag in metadata.shared_with: Return OK
5. Default: Return PermissionDenied
This pattern combines:
- Token-level permissions (scope)
- Resource-level ownership
- Resource-level sharing permissionsCross-Instance Authentication
ProxyToken Flow
When Alice (on instance A) wants to access Bob’s resource (on instance B):
- Alice’s client requests access from instance A
- Instance A creates a ProxyToken signed with its profile key
- Instance A sends ProxyToken to instance B:
POST /api/auth/proxy - Instance B validates ProxyToken:
- Fetches instance A’s public key
- Verifies signature
- Checks expiration
- Instance B creates AccessToken for Alice
- Instance B returns AccessToken to instance A
- Instance A returns AccessToken to Alice’s client
- Alice’s client uses AccessToken to access Bob’s resource directly on instance B
ProxyToken Verification
Algorithm: Verify ProxyToken
Input: JWT token string, requester_id_tag
Output: Result<ProxyTokenClaims>
1. Decode JWT without verification (read claims)
2. Fetch requester's profile:
- GET /api/me from requester's instance
- Extract public keys from profile
3. Find signing key:
- Look up key by key_id (kid) in claims
- If not found: Return KeyNotFound error
4. Verify signature:
- Use requester's public key to verify JWT signature
5. Check expiration:
- If exp < current_time: Return TokenExpired
6. Return verified claimsToken Generation
Creating an Access Token
Algorithm: Create Access Token
Input: tn_id, id_tag, resource_id, scope array, duration
Output: JWT token string
1. Build AccessTokenClaims:
- sub: User identity (id_tag)
- aud: Resource identifier (resource_id)
- exp: current_time + duration
- iat: current_time
- scope: scope array joined as space-separated string
- tid: Tenant ID (tn_id)
2. Sign JWT:
- Use AuthAdapter to create JWT
- Signed with instance's private key
3. Return token stringToken Refresh
Access tokens can be refreshed before expiration:
POST /api/auth/refresh
Authorization: Bearer <expiring_token>
Response:
{
"access_token": "eyJhbGc...",
"expires_in": 3600
}API Reference
POST /api/auth/token
Request an access token.
Request:
POST /api/auth/token
Content-Type: application/json
{
"resource_id": "f1~abc123...",
"scope": "read write",
"duration": 3600
}Response (200 OK):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write"
}POST /api/auth/refresh
Refresh an expiring access token.
Request:
POST /api/auth/refresh
Authorization: Bearer <current_token>Response (200 OK):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3600
}POST /api/auth/proxy
Request an access token on behalf of a user (cross-instance).
Request:
POST /api/auth/proxy
Content-Type: application/json
Authorization: Bearer <proxy_token>
{
"user_id_tag": "alice.example.com",
"resource_id": "f1~xyz789...",
"scope": "read"
}Response (200 OK):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3600
}Security Considerations
Token Storage
Client-Side:
- Store in memory or sessionStorage (not localStorage for better security)
- Never log tokens
- Clear on logout
Server-Side:
- JWT signing secrets in AuthAdapter (never exposed)
- Rotate signing secrets periodically
- Use strong secrets (256+ bits)
Token Transmission
- Always use HTTPS/TLS
- Include in
Authorization: Bearer <token>header - Never in URL query parameters
Token Revocation
Since JWTs are stateless, revocation requires:
- Short Lifetimes: Limit damage window (1-24 hours)
- Blacklisting: Maintain revoked token list (for critical cases)
- Key Rotation: Invalidates all tokens signed with old key
Rate Limiting
Protect token endpoints from abuse:
Token Endpoint Rate Limits:
-
MAX_TOKEN_REQUESTS_PER_HOUR: 100 (per user, per hour)- Prevents token request floods
- Typical usage: <10 requests/hour
-
MAX_REFRESH_PER_TOKEN: 10 (per token)- Limits how many times a single token can be refreshed
- Prevents refresh abuse
- Encourages re-authentication after N refreshes
See Also
- Identity System - Profile keys and cryptographic foundations
- [Actions](/architecture/actions-federation/actions - Action tokens and federation
- System Architecture - AuthAdapter and middleware