Authentication

Authentication

Every request needs an OAuth 2.0 access token, sent in the Authorization header:

curl "https://api.sandbox.theflowerfactory.ie/partner/v1/orders/WEB-100234" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Your credentials

When we set you up, we give you a client ID and a client secret. They identify your application and the account your orders belong to: you only ever see your own orders.

If your service runs on Kubernetes (such as AKS or EKS), or anywhere else that issues OpenID Connect tokens to workloads, you can authenticate without a secret instead.

Getting a token

Your service gets a token from Microsoft Entra ID, then sends it with each request until it’s close to expiring:

sequenceDiagram participant S as Your service participant E as Microsoft Entra ID participant A as The Flower Factory API S->>E: POST /oauth2/v2.0/token<br/>client ID, client secret, scope E-->>S: access token, lasts about an hour loop Until the token is close to expiring S->>A: Request with Authorization: Bearer token A-->>S: Response end

Get the token with the client credentials flow:

curl -X POST "https://login.microsoftonline.com/b66ad013-3458-4610-a433-7bdd1264b5b4/oauth2/v2.0/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "scope=api://24579c38-caed-4940-a35f-8fc7763b718c/.default"

The response has the token in access_token, and how many seconds it lasts in expires_in (about an hour). Reuse the token until it’s close to expiring, then get a new one: don’t get a new token for every request.

Most OAuth 2.0 libraries handle this for you. In .NET, for example, use MSAL’s ConfidentialClientApplicationBuilder (Microsoft.Identity.Client) with the same tenant and scope: it caches the token and gets a new one when it expires.

When a request is refused

  • 401 Unauthorized: the token is missing, invalid or expired. Get a new token and try again.
  • 403 Forbidden: the token is valid, but not for an application we’ve set up as a partner. Check you’re using the credentials we gave you for this environment.
Keep your client secret safe

Treat it like a password. Keep it on your server, never in a browser, mobile app or source control. If you think it has leaked, tell us and we’ll replace it.

Authenticating without a secret

With workload identity federation, your service proves who it is with a token its own platform gives it, so there’s no secret to store or rotate. Kubernetes gives each pod a signed token for its service account; Microsoft Entra ID checks that token against your cluster’s published keys and, if it’s from the service account we’ve set up, gives you an access token for this API. The access token is the same as one you’d get with a secret.

It works with AKS, EKS, GKE and any other platform whose OpenID Connect issuer is reachable from the internet.

sequenceDiagram participant K as Your cluster participant P as Your pod participant E as Microsoft Entra ID participant A as The Flower Factory API K->>P: Service account token, in a file P->>E: POST /oauth2/v2.0/token<br/>client ID, scope, service account token as client_assertion E->>K: Checks the token against your cluster's published keys E-->>P: access token, lasts about an hour P->>A: Request with Authorization: Bearer token A-->>P: Response

What to send us

  • Your cluster’s OIDC issuer URL:
    • AKS: az aks show --name CLUSTER --resource-group GROUP --query oidcIssuerProfile.issuerUrl --output tsv
    • EKS: aws eks describe-cluster --name CLUSTER --query cluster.identity.oidc.issuer --output text
  • The namespace and service account your service runs as.

We send you a client ID. Each cluster and service account needs setting up, so tell us about each one you’ll call from (up to 20).

AKS

Turn on the cluster’s OIDC issuer and workload identity, if they aren’t already:

az aks update --name CLUSTER --resource-group GROUP --enable-oidc-issuer --enable-workload-identity

Annotate the service account with the client ID we gave you and our tenant ID, and label your pods:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: tff-client
  namespace: orders
  annotations:
    azure.workload.identity/client-id: YOUR_CLIENT_ID
    azure.workload.identity/tenant-id: b66ad013-3458-4610-a433-7bdd1264b5b4
---
# In your Deployment's pod template:
metadata:
  labels:
    azure.workload.identity/use: "true"
spec:
  serviceAccountName: tff-client

The tenant ID annotation matters: it’s our tenant, not yours. Workload identity then gives your pods AZURE_CLIENT_ID, AZURE_TENANT_ID and AZURE_FEDERATED_TOKEN_FILE.

EKS

Mount a service account token for Microsoft Entra ID, and point the same variables at it:

spec:
  serviceAccountName: tff-client
  containers:
    - name: app
      env:
        - name: AZURE_TENANT_ID
          value: b66ad013-3458-4610-a433-7bdd1264b5b4
        - name: AZURE_CLIENT_ID
          value: YOUR_CLIENT_ID
        - name: AZURE_FEDERATED_TOKEN_FILE
          value: /var/run/secrets/tff/token
      volumeMounts:
        - name: tff-token
          mountPath: /var/run/secrets/tff
          readOnly: true
  volumes:
    - name: tff-token
      projected:
        sources:
          - serviceAccountToken:
              path: token
              audience: api://AzureADTokenExchange
              expirationSeconds: 3600

The audience must be api://AzureADTokenExchange. Kubernetes replaces the token before it expires, so read the file each time you get an access token rather than once at start-up.

Getting a token

The Azure Identity libraries do this for you from those three variables. In .NET:

using Azure.Core;
using Azure.Identity;

var credential = new WorkloadIdentityCredential();
var token = await credential.GetTokenAsync(
    new TokenRequestContext(["api://24579c38-caed-4940-a35f-8fc7763b718c/.default"]));
// Send token.Token as the Bearer token. The credential caches it and gets a new one when it expires.

Python (azure-identity), JavaScript (@azure/identity), Java and Go have a WorkloadIdentityCredential too. Without a library, send the Kubernetes token as a client assertion instead of a secret:

curl -X POST "https://login.microsoftonline.com/b66ad013-3458-4610-a433-7bdd1264b5b4/oauth2/v2.0/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "scope=api://24579c38-caed-4940-a35f-8fc7763b718c/.default" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  --data-urlencode "client_assertion@/var/run/secrets/tff/token"

If the exchange fails with AADSTS70021 (no matching federated identity record), the token’s issuer, service account or audience doesn’t match what we set up: check the namespace and service account name, and the audience on EKS.