User tokens: the token route
When your app should call the File Service as the signed-in user (to browse folders, search, or upload under the user’s own permissions), it needs a user token for the Files API. Your backend gets it by token exchange and hands it out on a route of its own. This page is for backend developers adding that route. The backend-mediated flow of the quickstart needs none of it.
files:accessWhy a second token
Section titled “Why a second token”The token your app holds is for your API: its audience is your API, and the File Service rejects it with invalid_token. Your backend trades it for a token of the same user whose audience is files-api and whose scope is files:access. The exchanged token:
- acts as the user: the user’s permissions apply, see Permissions;
- is bound to your backend client’s tenant and application;
- lives at most 10 minutes, so the app asks your route again before it expires;
- is a plain Bearer token, also for a mobile app whose own token is key-bound.
It is the same route as for Chat and Notifications, with the other resource and scope.
Add the route
Section titled “Add the route”-
Give your backend client the exchange permission. Step 6 of the checklist: token exchange onto the Files API,
files:access. -
Install the Identity Server client in your backend:
@central-code-station/ccs-identity-client, from the same registry as the Files client. -
Add
GET /files-tokenbehind the check that protects the rest of your API:files-token.ts import { CcsIdentityClient, CcsOAuthError } from '@central-code-station/ccs-identity-client';import { filesEndpoints } from '@central-code-station/ccs-files-client';import type { CurrentUser } from '../shared/app';// The Files API and its resource identifier (never the audience), as FilesClient computes them: production unless// FILES_API_URL / FILES_API_RESOURCE or CCS_ENVIRONMENT say otherwise.const { apiUrl, resource } = filesEndpoints({ env: process.env });const ccsIdentity = new CcsIdentityClient({issuerUrl: process.env.CCS_ISSUER_URL!,clientId: process.env.CCS_FILES_CLIENT_ID!, // the backend client that holds the token-exchange permission files:accessclientSecret: process.env.CCS_FILES_CLIENT_SECRET!,});// GET /files-token, behind your normal access-token check: the user's own token is already verified.export async function filesToken(user: CurrentUser): Promise<{ status: number; headers: Record<string, string>; body: unknown }> {try {const { access_token, expires_in } = await ccsIdentity.exchangeToken({subjectToken: user.accessToken, // the token your app sent to your APIresource, // https://files.centralcodestation.com/api in productionscope: 'files:access',});return {status: 200,headers: { 'Cache-Control': 'no-store' },body: { access_token, expires_in, api_url: apiUrl, tenant_id: process.env.CCS_TENANT_ID, application_id: process.env.CCS_APPLICATION_ID },};} catch (error) {// invalid_grant (the user's token expired), unauthorized_client or invalid_target (setup): never retry blindly.if (error instanceof CcsOAuthError) return { status: 401, headers: {}, body: { error: error.error } };throw error;}}Answer
Cache-Control: no-store. The optionalapi_url,tenant_idandapplication_idtell the app where to call, so it needs no configuration for the service.
Use it in the app
Section titled “Use it in the app”The user clients do this for you: createTokenProvider and FilesClient.fromTokenRoute in the web client, TokenProvider and FilesClient.fromTokenRoute in the Flutter package. Any other client asks the route for a token, caches it until about a minute before expires_in runs out, and sends it as Authorization: Bearer to the API. Ask again on a 401. One request at a time is enough: share the pending request between callers.
curl -s "$FILES_API_URL/tenants/$CCS_TENANT_ID/applications/$CCS_APPLICATION_ID/me/capabilities" \ -H "Authorization: Bearer $USER_FILES_TOKEN"# {"tenant_id":"fitness-co","application_id":"fitness-app","user_id":"user-ana","principal_type":"user","tenant_owner":false,"roles":[...],"capabilities":["files.view"]}GET P/me/capabilities tells the app what the user may do everywhere in the application; every file and folder also carries my_actions, the user’s actions on that item.
Errors from the exchange
Section titled “Errors from the exchange”| Error | Cause | Fix |
|---|---|---|
unsupported_grant_type |
Token exchange is off on the Identity Server | Ask its operator to turn it on |
unauthorized_client |
The client lacks the grant type or the exchange permission | Step 6 of the checklist |
invalid_target |
resource is the audience instead of the identifier, or Files is off for the application |
Send https://files.centralcodestation.com/api; check step 3 |
invalid_scope |
A scope other than files:access was asked for |
Only files:access can be exchanged |
invalid_grant |
The user’s own token expired or was revoked | The app signs the user in again |
Next steps
Section titled “Next steps”- Contexts and tokens for the token rules.
- Permissions for what a user token may do.
- Upload protocol for an app that uploads as the user.