API Authentication#

Note

This document is about API authentication for anyone except robots. For robots, refer to HMAC Auth.

Quickstart#

For Python users, the quickest way to get started is with the SDK, which will handle authentication for you. Just create a client with the appropriate environment and you’re good to go:

# Requires destiny-sdk>=0.13.0
from destiny_sdk.client import OAuthClient
client = OAuthClient(env="staging")
response = client.search(query="example")
print(response)

Read on for the underlying OAuth2 flow, alternative authentication methods (Postman, curl, service accounts), and overriding configuration.

Background#

Interaction with the DESTINY Repository API requires first obtaining an authentication token from the DESTINY authentication server (a Keycloak instance). This token must then be included in the Authorization header of each API request.

        sequenceDiagram
    actor Client
    participant Auth Server
    participant API
    Client->>Auth Server: Request token (client credentials)
    Auth Server-->>Client: Return access token
    Client->>API: API request with Authorization: Bearer <token>
    API-->>Client: Return requested data
    

Provisioning#

In order to obtain a token from the DESTINY authentication server, you will need to be enrolled in our auth server. Please reach out if you need access.

Everyone enrolled will have reference.reader, but please reach out if you need additional permissions, naming the environments they apply to. You can see which permission each API resource requires in the API documentation - it is listed under each sub-category.

Obtaining a token#

Interactive user authentication#

For human users logging in with their own credentials. Uses the OAuth 2.0 authorization code flow with PKCE; the user is redirected to a browser to authenticate.

Using the SDK#

This is the recommended way to obtain tokens, as the SDK will handle token caching and refreshing for you, and will be kept up to date with any changes to the API authentication process.

The only information you need to authenticate is the environment you want to access (development, staging, or production). Other configuration is overwritable but will default to the correct values for each environment.

from destiny_sdk.client import OAuthMiddleware

auth = OAuthMiddleware(env="production")

See OAuthMiddleware for the full set of configuration options.

Using another method#

If you are not using Python, or want to authenticate from a tool like Postman, you can drive Keycloak’s OAuth 2.0 authorization code flow with PKCE directly. The values below configure any standards-compliant OAuth 2.0 client.

Keycloak Configuration#

Field

Value

Authorization endpoint

https://auth.evidence-repository.org/realms/destiny/protocol/openid-connect/auth

Token endpoint

https://auth.evidence-repository.org/realms/destiny/protocol/openid-connect/token

Realm

destiny

Client ID (development)

destiny-auth-client-development

Client ID (staging)

destiny-auth-client-staging

Client ID (production)

destiny-auth-client-production

Grant type

Authorization Code (with PKCE)

Code challenge method

S256

Scopes

openid profile email

Request no other scopes - your permissions are already carried in the token.

The redirect URI you supply must be registered on the Keycloak client. At present we allow localhost and postman redirect URIs, but if you want to use a different one, please reach out so we can add it.

Service-to-service authentication#

For backend services, scheduled jobs, or anything else that runs without a human user. Uses the OAuth 2.0 client credentials flow. This requires a dedicated Keycloak client with Service Accounts enabled — please reach out so we can provision one, telling us which environments it needs and what it needs to do there.

Using the SDK#

from pydantic import SecretStr
from destiny_sdk.client import OAuthMiddleware

auth = OAuthMiddleware(
    client_id="my-service-client",
    client_secret=SecretStr("..."),
)

The SDK detects client_secret and switches to the client credentials flow automatically. env is not used in this mode.

Using another method#

A single POST against the token endpoint:

curl -X POST https://auth.evidence-repository.org/realms/destiny/protocol/openid-connect/token \
    -d grant_type=client_credentials \
    -d client_id=$DESTINY_CLIENT_ID \
    -d client_secret=$DESTINY_CLIENT_SECRET \
    -d scope=openid

The response contains access_token. Unlike the interactive flow there is no refresh_token — re-POST the same request when the token expires.

Using the token#

The API base URL for each environment is as follows:

Using the SDK#

Again, we recommend using the SDK to make API requests, as it will handle including the token for you. Some endpoints will have convenience methods available, otherwise you can access the underlying httpx client directly.

class destiny_sdk.client.OAuthClient(base_url: HttpUrl | str | None = None, auth: Auth | None = None, timeout: int = 10, *, env: Literal['development', 'staging', 'production'] | None = None)[source]

Client for interaction with the Destiny API using OAuth2.

This will apply the provided authentication, usually OAuthMiddleware, to all requests. Some API endpoints are supported directly through methods on this class, while others can be accessed through the underlying httpx client.

Example usage:

from destiny_sdk.client import OAuthClient

# Either use env for defaults or provide explicit auth and base_url
client = OAuthClient(env="production")
client = OAuthClient(
    base_url="https://destiny-repository.example.com",
    auth=OAuthMiddleware(...),
)

# Supported method
response = client.search(query="example")

# Unsupported method, use underlying httpx client
response = client.get_client().get("/system/healthcheck/")

Using directly#

When making API requests, include the token in the Authorization header following Bearer, eg:

curl https://api.evidence-repository.org/v1/references/search/?q=example \
    -H "Authorization: Bearer $ACCESS_TOKEN"

The tokens will expire after a certain period (usually two hours). After expiration, you will need to obtain a new token using the same method as before.

Troubleshooting#

A 401 means the token was rejected - most often expired, or obtained for a different environment than the API you called. A 403 means you lack the permission that resource requires.

Please reach out if you experience any issues either obtaining or using tokens - most likely, we need to update some permissions.