Identity API
Aspire name api-identity. The only service that signs tokens.
No endpoint here enforces a permission — every one exists to establish an identity that does not yet exist, so requiring a token would fail the very request that issues one. This service must not be reachable from outside the cluster.
Mint a token from a login
{
"userId": "9c1e7a4d-…",
"provider": "Entra",
"providerUserId": "…",
"name": "Neil Docherty",
"email": "neil@example.com",
"orgId": "05cc0c65-…",
"profileImage": null
}
Returns the token as text/plain.
| Status | Meaning |
|---|---|
200 | The token |
403 | There is nobody to issue a token to, and no account pattern permits creating them. Not a fault in the request — the caller should end the session, not report a problem with it. |
400 | The request was wrong |
The generator reads the account and its permissions from the account API, then mints. sub is the TagShape
account ID, not the provider's identifier. Where the account exists, its name and email win over what the
provider said, so a rename reaches the token on the login that noticed it.
Renew a token
{ "userId": "9c1e7a4d-…", "orgId": "05cc0c65-…" }
Re-reads permissions from GET /users/{id}/permissions before minting, so a permission granted or withdrawn takes
effect at the next refresh. Creates nothing and changes nothing — provisioning only ever happens on a login.
Exchange an API key
curl -i -X POST "$IDENTITY_API/apikeyjwt" -H "x-tagshape-api: $API_KEY"
HTTP/1.1 200 OK
x-tagshape-auth: eyJhbGciOiJSUzI1NiIs...
The key travels in a header so the exchange looks like every other call the caller makes, and the token comes back in one for the same reason.
| Status | Meaning |
|---|---|
200 | Token in x-tagshape-auth |
401 | No key, a key that named nobody, a revoked key, or an expired key — all the same answer, so nothing tells a prober which of their guesses was once real |
500 | The key was accepted but signing failed. Deliberately not a 401, which would send somebody off rotating a key that was fine. |
A token minted from a key is indistinguishable from one minted from a login. It cannot ask for a session as somebody else, or in an organization that is not its own.
Generate a signing key
curl "$IDENTITY_API/generatejwk/my-key-id"
Returns a public PEM, a private PEM and a JWK. An operational tool run out of band — not part of the request
path. The private PEM becomes Jwt:PrivateKey on this service.
Signing and lifetime
RS256. Configured under Jwt:
| Setting | Default |
|---|---|
Issuer | auth@tagshape.com |
Audience | tagshape.com |
DurationInMinutes | 60 |
RefreshDurationInMinutes | 10080 |
PrivateKey | (required) |
See JWT for the claim set and Configuration.
Auditing
Every mint writes a JWT audit entry with the action Create or Refresh. Those written during a login belong
to no organization yet, which is why reading them needs AuditGlobalList and AuditGlobalRead rather than the
organizational pair — see Audit entries.