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:

{
  "iss": "alice.example.com",   // Issuer (identity of the node)
  "sub": "alice.example.com",   // Subject (user identity, for cross-instance)
  "exp": 1738483200,            // Expiration timestamp
  "scope": "resource_id",       // Scope (resource identifier)
  "r": "USR"                    // Roles
}

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:

  1. The user’s node requests an access token.
  2. If the resource is local, the node issues the token directly.
  3. If the resource is remote, the node authenticates with the remote node and requests a token on behalf of the user.
  4. 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:

  1. Extract Token: Parse JWT from Authorization header
  2. Decode JWT: Parse header and claims (no verification yet)
  3. Verify Signature: Validate using AuthAdapter-stored secret
  4. Check Expiration: Ensure exp > current time
  5. Validate Claims: Check aud, scope, tid
  6. Create Auth Context: Build Auth struct for handler
pub struct AuthCtx {
    pub tn_id: TnId,                   // Tenant ID (database key)
    pub id_tag: Box<str>,              // Identity tag (e.g., "alice.example.com")
    pub roles: Box<[Box<str>]>,        // Roles (e.g., ["SADM", "USR"])
    pub scope: Option<Box<str>>,       // Optional scope (e.g., "apkg:publish")
}

Custom Extractors

Axum extractors provide typed access to authentication context:

TnId Extractor:

  • struct TnId(pub u32) - 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 identifier (TnId(u32))
  • id_tag: User identity (e.g., “alice.example.com”)
  • roles: Assigned roles (e.g., [“SADM”, “USR”])
  • scope: Optional scope string (e.g., “apkg:publish”)

Usage: Check auth.roles for role-based access, auth.scope for scoped API key permissions

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:

  1. File-Level: Who can access a file
  2. Database-Level: Who can access a database (RTDB)
  3. Action-Level: Who can see an action token
  4. API-Level: Rate limiting, quota enforcement

Permission checks combine token scope, resource ownership, and sharing permissions. See ABAC Permission System for the full evaluation flow.

Cross-Instance Authentication

ProxyToken Flow

When Alice (on instance A) wants to access Bob’s resource (on instance B):

  1. Alice’s client requests access from instance A
  2. Instance A creates a ProxyToken signed with its profile key
  3. Instance A sends ProxyToken to instance B: POST /api/auth/proxy
  4. Instance B validates ProxyToken:
    • Fetches instance A’s public key
    • Verifies signature
    • Checks expiration
  5. Instance B creates AccessToken for Alice
  6. Instance B returns AccessToken to instance A
  7. Instance A returns AccessToken to Alice’s client
  8. 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 claims

Token Lifecycle

Access tokens are JWTs signed with the instance’s private key. They can be refreshed before expiration via POST /api/auth/refresh.

API Reference

GET /api/auth/access-token

Request an access token.

Request:

GET /api/auth/access-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-token

Request an access token on behalf of a user (cross-instance).

Request:

POST /api/auth/proxy-token
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
}

See Also