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:
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.
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.
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
- AKS:
- 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.