Auth API
Base URL: https://auth.cloud.eddisonso.com
Authentication
POST /api/login
Authenticate and receive a session JWT.
Auth: None
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| username | string | body | Yes | Username |
| password | string | body | Yes | Password |
Example request:
curl -X POST https://auth.cloud.eddisonso.com/api/login \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "secret"}'
Response:
If the user has no 2FA configured:
{
"username": "alice",
"display_name": "Alice",
"user_id": "abc123",
"is_admin": false,
"token": "eyJhbGci..."
}
If the user has security keys registered (2FA required):
{
"requires_2fa": true,
"challenge_token": "eyJhbGci..."
}
Use the challenge_token with the WebAuthn login endpoints to complete 2FA.
GET /api/session
Get current user info from session token.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| Authorization | string | header | Yes | Bearer <token> |
Example request:
curl https://auth.cloud.eddisonso.com/api/session \
-H "Authorization: Bearer eyJhbGci..."
Response:
{
"username": "alice",
"display_name": "Alice",
"user_id": "abc123",
"is_admin": false
}
POST /api/logout
Acknowledge logout (client removes token).
Auth: None
Example request:
curl -X POST https://auth.cloud.eddisonso.com/api/logout
Response:
{
"status": "ok"
}
WebAuthn (2FA Login)
POST /api/webauthn/login/begin
Begin WebAuthn authentication ceremony (2FA). Requires a challenge token from the initial login response.
Auth: Challenge token
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| Authorization | string | header | Yes | Bearer <challenge_token> |
Example request:
curl -X POST https://auth.cloud.eddisonso.com/api/webauthn/login/begin \
-H "Authorization: Bearer eyJhbGci..."
Response:
{
"options": {
"challenge": "...",
"timeout": 60000,
"rpId": "cloud.eddisonso.com",
"allowCredentials": [...]
},
"state": "ceremony_state_token"
}
Pass options to navigator.credentials.get() in the browser, then send the credential to /api/webauthn/login/finish.
POST /api/webauthn/login/finish
Complete WebAuthn authentication and receive a session token.
Auth: None
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| state | string | body | Yes | State token from begin response |
| credential | object | body | Yes | Serialized credential from browser |
Example request:
curl -X POST https://auth.cloud.eddisonso.com/api/webauthn/login/finish \
-H "Content-Type: application/json" \
-d '{
"state": "ceremony_state_token",
"credential": {...}
}'
Response:
{
"username": "alice",
"display_name": "Alice",
"user_id": "abc123",
"is_admin": false,
"token": "eyJhbGci..."
}
User Settings
GET /api/settings/keys
List all security keys (WebAuthn credentials) for the authenticated user.
Auth: Session
Example request:
curl https://auth.cloud.eddisonso.com/api/settings/keys \
-H "Authorization: Bearer eyJhbGci..."
Response:
{
"keys": [
{
"id": "base64_credential_id",
"name": "YubiKey 5",
"authenticator_type": "Security Key",
"created_at": 1712224000
}
]
}
POST /api/settings/keys/add/begin
Begin WebAuthn registration ceremony to add a new security key.
Auth: Session
Example request:
curl -X POST https://auth.cloud.eddisonso.com/api/settings/keys/add/begin \
-H "Authorization: Bearer eyJhbGci..."
Response:
{
"options": {
"challenge": "...",
"rp": {"name": "Edd Cloud", "id": "cloud.eddisonso.com"},
"user": {...},
"pubKeyCredParams": [...],
"timeout": 60000
},
"state": "ceremony_state_token"
}
POST /api/settings/keys/add/finish
Complete WebAuthn registration and save the new security key.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| state | string | body | Yes | State token from begin response |
| credential | object | body | Yes | Serialized credential from browser |
| name | string | body | No | Custom name for the key (default: "") |
Example request:
curl -X POST https://auth.cloud.eddisonso.com/api/settings/keys/add/finish \
-H "Authorization: Bearer eyJhbGci..." \
-H "Content-Type: application/json" \
-d '{
"state": "ceremony_state_token",
"credential": {...},
"name": "YubiKey 5"
}'
Response: 200 OK with empty body.
POST /api/settings/keys/delete
Delete a security key.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| id | string | body | Yes | Base64-encoded credential ID |
Example request:
curl -X POST https://auth.cloud.eddisonso.com/api/settings/keys/delete \
-H "Authorization: Bearer eyJhbGci..." \
-H "Content-Type: application/json" \
-d '{"id": "base64_credential_id"}'
Response: 200 OK with empty body.
POST /api/settings/keys/rename
Rename a security key.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| id | string | body | Yes | Base64-encoded credential ID |
| name | string | body | Yes | New name for the key |
Example request:
curl -X POST https://auth.cloud.eddisonso.com/api/settings/keys/rename \
-H "Authorization: Bearer eyJhbGci..." \
-H "Content-Type: application/json" \
-d '{"id": "base64_credential_id", "name": "Work YubiKey"}'
Response: 200 OK with empty body.
PUT /api/settings/profile
Update display name for the authenticated user.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| display_name | string | body | Yes | New display name |
Example request:
curl -X PUT https://auth.cloud.eddisonso.com/api/settings/profile \
-H "Authorization: Bearer eyJhbGci..." \
-H "Content-Type: application/json" \
-d '{"display_name": "Alice Smith"}'
Response:
{
"username": "alice",
"display_name": "Alice Smith",
"user_id": "abc123"
}
PUT /api/settings/password
Change password for the authenticated user.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| current_password | string | body | Yes | Current password |
| new_password | string | body | Yes | New password |
Example request:
curl -X PUT https://auth.cloud.eddisonso.com/api/settings/password \
-H "Authorization: Bearer eyJhbGci..." \
-H "Content-Type: application/json" \
-d '{"current_password": "old", "new_password": "new"}'
Response: 200 OK with empty body.
GET /api/settings/sessions
List all active sessions for the authenticated user.
Auth: Session
Example request:
curl https://auth.cloud.eddisonso.com/api/settings/sessions \
-H "Authorization: Bearer eyJhbGci..."
Response:
[
{
"id": 123,
"ip_address": "203.0.113.42",
"created_at": 1712224000,
"is_current": true
},
{
"id": 122,
"ip_address": "198.51.100.10",
"created_at": 1712220000,
"is_current": false
}
]
Sessions automatically expire when their JWT token expires. There is no manual revocation endpoint.
API Tokens
POST /api/tokens
Create a new API token.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| name | string | body | Yes | Token name (max 64 chars) |
| scopes | object | body | Yes | Scope-to-actions map (see Token Scopes) |
| expires_in | string | body | No | "30d", "90d", "365d", or "never" (default "never") |
Each scope key follows the format <service>.<user_id>[.<resource>[.<id>]] and maps to an array of actions: "create", "read", "update", "delete".
Example request:
curl -X POST https://auth.cloud.eddisonso.com/api/tokens \
-H "Authorization: Bearer eyJhbGci..." \
-H "Content-Type: application/json" \
-d '{
"name": "ci-deploy",
"scopes": {
"compute.abc123.containers": ["read", "create", "delete"]
},
"expires_in": "90d"
}'
Response:
{
"id": "tok_5f3a",
"name": "ci-deploy",
"scopes": {
"compute.abc123.containers": ["read", "create", "delete"]
},
"expires_at": 1720000000,
"created_at": 1712224000,
"last_used_at": 0,
"token": "ecloud_eyJhbGci..."
}
The token field is only returned on creation. Store it — it cannot be retrieved later.
GET /api/tokens
List all API tokens for the authenticated user.
Auth: Session
Example request:
curl https://auth.cloud.eddisonso.com/api/tokens \
-H "Authorization: Bearer eyJhbGci..."
Response:
[
{
"id": "tok_5f3a",
"name": "ci-deploy",
"scopes": {
"compute.abc123.containers": ["read", "create", "delete"]
},
"expires_at": 1720000000,
"created_at": 1712224000,
"last_used_at": 1712300000,
"service_account_id": null
}
]
For tokens bound to a service account, service_account_id will contain the service account ID instead of null.
DELETE /api/tokens/{id}
Delete an API token.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| id | string | path | Yes | Token ID |
Example request:
curl -X DELETE https://auth.cloud.eddisonso.com/api/tokens/tok_5f3a \
-H "Authorization: Bearer eyJhbGci..."
Response:
{
"status": "ok"
}
GET /api/tokens/{id}/check
Service-to-service endpoint to check whether an API token is valid and not revoked.
Auth: None
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| id | string | path | Yes | Token ID |
Example request:
curl https://auth.cloud.eddisonso.com/api/tokens/tok_5f3a/check
Response:
{
"status": "valid"
}
Returns 404 if the token is not found, revoked, or expired.
For service account tokens, the response includes the service account's current scopes:
{
"status": "valid",
"scopes": {
"compute.abc123.containers": ["read", "create"]
}
}
Docker Registry Token
GET /v2/token
Issues a short-lived JWT for Docker Token Authentication. This endpoint is called automatically by Docker/OCI clients when accessing registry.cloud.eddisonso.com. It is not typically called directly.
Auth: HTTP Basic (optional — omit for anonymous/public access)
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| service | string | query | Yes | Registry hostname (e.g. registry.cloud.eddisonso.com) |
| scope | string | query | No | OCI scope string (e.g. repository:ecloud-auth:pull,push) |
Example request (user login):
curl -u "eddison:mypassword" \
"https://auth.cloud.eddisonso.com/v2/token?service=registry.cloud.eddisonso.com&scope=repository:ecloud-auth:pull,push"
Example request (service account):
curl -u "my-sa:ecloud_eyJhbGci..." \
"https://auth.cloud.eddisonso.com/v2/token?service=registry.cloud.eddisonso.com&scope=repository:ecloud-auth:pull"
Response:
{
"token": "eyJhbGci...",
"expires_in": 300,
"issued_at": "2026-03-15T12:00:00Z"
}
The returned token is a JWT valid for 5 minutes. The access claim contains the granted OCI scopes. Actions not permitted (e.g. push to another user's private repo) are silently dropped from the granted set — the token is still issued, just with reduced scope.
Access rules:
| Identity | Pull (public) | Pull (private) | Push |
|---|---|---|---|
| Anonymous | Yes | No | No |
| Authenticated user | Yes | Own repos | Own repos |
| Service account | Yes | Scoped repos | Scoped repos |
For service account tokens, scopes are mapped from the storage.<userID>.registry.<repo> scope format to OCI pull/push actions. A wildcard storage.<userID>.registry scope grants access to all of that user's repositories.
Service Accounts
Service accounts separate identity and permissions from credentials. Create a service account with scoped permissions, then generate tokens that inherit those permissions. Updating a service account's scopes takes effect for all its tokens within the 5-minute cache TTL.
POST /api/service-accounts
Create a new service account.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| name | string | body | Yes | Service account name (max 64 chars) |
| scopes | object | body | Yes | Scope-to-actions map (same format as token scopes) |
Example request:
curl -X POST https://auth.cloud.eddisonso.com/api/service-accounts \
-H "Authorization: Bearer eyJhbGci..." \
-H "Content-Type: application/json" \
-d '{
"name": "ci-pipeline",
"scopes": {
"compute.abc123.containers": ["read", "create", "delete"]
}
}'
Response:
{
"id": "xK9mPq",
"name": "ci-pipeline",
"scopes": {
"compute.abc123.containers": ["read", "create", "delete"]
},
"token_count": 0,
"created_at": 1712224000
}
GET /api/service-accounts
List all service accounts for the authenticated user.
Auth: Session
Example request:
curl https://auth.cloud.eddisonso.com/api/service-accounts \
-H "Authorization: Bearer eyJhbGci..."
Response:
[
{
"id": "xK9mPq",
"name": "ci-pipeline",
"scopes": {
"compute.abc123.containers": ["read", "create", "delete"]
},
"token_count": 2,
"created_at": 1712224000
}
]
GET /api/service-accounts/{id}
Get a service account by ID.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| id | string | path | Yes | Service account ID |
Response: Same format as list item above.
PUT /api/service-accounts/{id}/scopes
Update a service account's scopes. Changes take effect for all tokens within the 5-minute cache TTL.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| id | string | path | Yes | Service account ID |
| scopes | object | body | Yes | New scope-to-actions map |
Example request:
curl -X PUT https://auth.cloud.eddisonso.com/api/service-accounts/xK9mPq/scopes \
-H "Authorization: Bearer eyJhbGci..." \
-H "Content-Type: application/json" \
-d '{
"scopes": {
"compute.abc123.containers": ["read"],
"storage.abc123.files": ["read"]
}
}'
Response:
{
"status": "ok"
}
DELETE /api/service-accounts/{id}
Delete a service account. All its tokens are revoked (cascade delete).
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| id | string | path | Yes | Service account ID |
Example request:
curl -X DELETE https://auth.cloud.eddisonso.com/api/service-accounts/xK9mPq \
-H "Authorization: Bearer eyJhbGci..."
Response:
{
"status": "ok"
}
POST /api/service-accounts/{id}/tokens
Create a token for a service account. The token inherits the service account's scopes (no scopes in the request).
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| id | string | path | Yes | Service account ID |
| name | string | body | Yes | Token name (max 64 chars) |
| expires_in | string | body | No | "30d", "90d", "365d", or "never" |
Example request:
curl -X POST https://auth.cloud.eddisonso.com/api/service-accounts/xK9mPq/tokens \
-H "Authorization: Bearer eyJhbGci..." \
-H "Content-Type: application/json" \
-d '{"name": "production", "expires_in": "365d"}'
Response:
{
"id": "tK3nAb",
"name": "production",
"expires_at": 1743760000,
"created_at": 1712224000,
"last_used_at": 0,
"token": "ecloud_eyJhbGci..."
}
The token field is only returned on creation. Store it — it cannot be retrieved later.
GET /api/service-accounts/{id}/tokens
List all tokens for a service account.
Auth: Session
| Param | Type | In | Required | Description |
|---|---|---|---|---|
| id | string | path | Yes | Service account ID |
Example request:
curl https://auth.cloud.eddisonso.com/api/service-accounts/xK9mPq/tokens \
-H "Authorization: Bearer eyJhbGci..."
Response:
[
{
"id": "tK3nAb",
"name": "production",
"expires_at": 1743760000,
"created_at": 1712224000,
"last_used_at": 1712300000
}
]