Service-to-Service Authentication¶
Consuming applications authenticate with the Identity Service using the X-Service-Key header. This mechanism ensures that only authorized backend services can call permission and role endpoints, while also identifying which application is making the request.
Service Apps¶
Service API keys are managed through the service apps system, not environment variables. Each service app is created via the admin panel (/admin/service-apps) or admin API and is bound to a specific service_name.
When a service app is created, a plaintext API key (prefixed with sk_) is returned once. The key is stored as a SHA-256 hash and cannot be retrieved again. Use the key in the X-Service-Key header:
The Identity Service validates the key by hashing it and matching against active service apps via service_app_service.validate_key(). If the key is missing, invalid, or belongs to an inactive app, the request is rejected with 401 Unauthorized.
Service App Fields¶
| Field | Description |
|---|---|
name |
Human-readable label for the application |
service_name |
The service this key is scoped to (e.g., "docu-store") |
key_hash |
SHA-256 hash of the plaintext key |
key_prefix |
First few characters for identification (e.g., sk_a1b2****) |
is_active |
Can be deactivated to block requests without deleting |
last_used_at |
Timestamp of last successful validation |
Managing Service Apps¶
POST /admin/service-apps # Create (returns plaintext key once)
GET /admin/service-apps # List all
GET /admin/service-apps/{id} # Get one
PATCH /admin/service-apps/{id} # Update name or is_active
DELETE /admin/service-apps/{id} # Delete
POST /admin/service-apps/{id}/rotate-key # Rotate key (returns new plaintext key)
X-Service-Key Header¶
Every request to the /permissions/* and /roles/* endpoints must include a valid service API key:
Service Name Scoping¶
Each key is bound to a service_name. The verify_service_scope() dependency ensures the key is authorized for the service being accessed. For example, a key created for "docu-store" cannot be used to register actions for "analytics".
Auth Tiers¶
Permission and role endpoints use two authentication tiers depending on the operation:
| Tier | Authentication Required | Endpoints |
|---|---|---|
| Dual auth | X-Service-Key + user Authorization: Bearer JWT |
POST /permissions/check |
POST /permissions/accessible |
||
POST /permissions/{id}/share |
||
| Service-only | X-Service-Key only |
POST /permissions/register |
PATCH /permissions/{id}/visibility |
||
DELETE /permissions/{id}/share |
||
GET /permissions/resource/{service}/{type}/{id} |
Dual Auth Endpoints¶
These endpoints need both a service key (to authenticate the calling service) and a user JWT (to identify the user whose permissions are being checked or modified). The user context (user ID, workspace ID, workspace role, groups) is extracted from the JWT.
Example request:
curl -X POST https://identity.example.com/permissions/check \
-H "X-Service-Key: sk_a1b2c3d4e5f6..." \
-H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" \
-d '{
"checks": [{
"service_name": "docu-store",
"resource_type": "document",
"resource_id": "doc-uuid",
"action": "edit"
}]
}'
Service-Only Endpoints¶
These endpoints perform administrative operations on behalf of the service (registering resources, managing visibility, revoking shares). They do not require a user JWT because the action is taken by the service itself, not on behalf of a specific user.
Example request:
curl -X POST https://identity.example.com/permissions/register \
-H "X-Service-Key: sk_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{
"service_name": "docu-store",
"resource_type": "document",
"resource_id": "new-doc-uuid",
"workspace_id": "workspace-uuid",
"owner_id": "creator-user-uuid",
"visibility": "workspace"
}'
Dev Mode¶
When no active service apps exist in the database (the default for a fresh installation), the service key requirement is disabled entirely. All requests pass through the require_service_key dependency without validation.
This makes local development easier -- you do not need to create service apps to test permission endpoints.
Important: In production, always register at least one service app via the admin panel. Having zero active service apps means no enforcement, which effectively disables service authentication.
Production Configuration¶
Creating a Service App¶
- Log in to the admin panel at
{ADMIN_URL} - Navigate to Service Apps
- Click Create Service App and provide a name and service name
- Copy the plaintext key from the response -- it is shown only once
Or via the admin API:
curl -X POST https://identity.example.com/admin/service-apps \
-H "Cookie: admin_token=..." \
-H "Content-Type: application/json" \
-d '{"name": "Docu-Store Production", "service_name": "docu-store"}'
The response includes the plaintext key:
{
"id": "...",
"name": "Docu-Store Production",
"service_name": "docu-store",
"key_prefix": "sk_a1b2****",
"key": "sk_a1b2c3d4e5f6g7h8..."
}
Key Rotation¶
Rotate a key without downtime:
- Call
POST /admin/service-apps/{id}/rotate-keyto generate a new key (the old key is immediately invalidated) - Update all consuming services to use the new key
For zero-downtime rotation, create a second service app with the same service_name, update consuming services, then deactivate the old app.
SDK Integration¶
The Python SDK's PermissionClient and RoleClient handle service key headers automatically:
from sentinel_auth.permissions import PermissionClient
client = PermissionClient(
base_url="https://identity.example.com",
service_name="docu-store",
service_key="sk_a1b2c3d4e5f6...",
)
# Dual auth: pass the user's JWT token
result = await client.can(
token="user-jwt-here",
resource_type="document",
resource_id="doc-uuid",
action="edit",
)
# Service-only: no token needed
await client.register_resource(
resource_type="document",
resource_id="new-doc-uuid",
workspace_id="workspace-uuid",
owner_id="creator-uuid",
)
The SDK's _headers(token=None) method builds the appropriate headers:
- Always includes
X-Service-Keyif aservice_keywas provided. - Includes
Authorization: Bearer {token}when a user token is passed.