Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

Storing and using keys

A key worth stealing

The clinic now depends on private keys. One signs its requests to the lab. Another belongs to the clinic's sign-in service, which signs the tokens that tell the clinic's apps who has signed in. Whoever holds that second key can create a token saying they are any patient, and every app that trusts the clinic will believe it.

Public keys can be shared freely. Private keys concentrate trust, and protecting them is mostly a question of where they live, who and what can use them, and how they are replaced when the time comes.

Where keys live

The simplest arrangement is a key file on the server, or a key in an environment variable. The application reads it at startup and signs with it directly. It is easy to set up, and easy to leak. The key can end up in backups, container images, source control, crash dumps, or a support screenshot. Anyone who can read the process's files or memory can copy it, and a copied key keeps working wherever the attacker takes it.

A secret manager improves on this by storing the key centrally, restricting which services may read it, and recording each access. The application still receives the key itself, so a compromised application can still copy it. What changes is who can reach it, and the record of who did.

Dedicated key hardware changes the model. A hardware security module (HSM), a cloud key management service, a computer's Trusted Platform Module, or a phone's secure element can generate a key that is marked non-exportable. The application never sees the key. It sends data to the hardware and asks for a signature, and the hardware checks that the caller is allowed to use that key.

# Key in a file: the application holds the private key
private_key = load_pem("/etc/clinic/token-signing.pem")
signature = sign(private_key, token_bytes)

# Non-exportable key: the application asks for a signature
signature = key_service.sign(key_name="clinic-token-signing-2026-09",
                             algorithm="ES256", data=token_bytes)

An attacker who takes over the application server can still ask the key service to sign while they have that access. They cannot take the key with them, and once the access is removed, their ability to sign ends. That makes the incident smaller and much easier to recover from.

Passkeys follow the same idea on a personal scale. The private key stays in the device's secure hardware or in the platform's credential store, and the device unlocks its use after a fingerprint, face, or PIN check.

Limiting each key

Using one key for everything is convenient and dangerous. If the clinic's token-signing key also signed lab requests and decrypted stored files, one leak would affect all three, and a message signed for one purpose might be accepted for another. Giving each key a single purpose limits both problems.

The same thinking applies to environments and customers. Development and production should never share keys, so a laptop test cannot sign a token that production accepts. A service that signs on behalf of separate organizations can give each its own key, so one organization's verifiers never accept another's tokens.

Permissions matter too. The sign-in service needs permission to sign with its key; very few people need permission to manage, export, or delete it. Records of which service used each key, and when, make unusual use visible.

Publishing public keys

The clinic's apps need the public half of the token-signing key. The clinic could send it to each app by hand, but it will eventually add apps and change keys. Instead, many services publish their current public keys at a fixed HTTPS address as a JSON Web Key Set (JWKS). This fictional example contains two keys:

GET https://login.harborclinic.example/.well-known/jwks.json

{
  "keys": [
    {
      "kid": "clinic-2026-09",
      "kty": "EC",
      "crv": "P-256",
      "alg": "ES256",
      "use": "sig",
      "x": "q8vX2mPjT0lR5aZkYc3NwB7eHsUd1fGiVoKn4xLtE9M",
      "y": "Jr6WnC0sDfu2hQkP8aTzLb5mEyVx3gNoRi7cK1dHtSA"
    },
    {
      "kid": "clinic-2026-06",
      "kty": "EC",
      "crv": "P-256",
      "alg": "ES256",
      "use": "sig",
      "x": "Tn4uB8yLdQ2rWk6sZcE0mHfV9aXpJ3gOiK7tY1eNvRw",
      "y": "c5LhG0pXq8zAeK2wMbT6vUnR3jYsD9fIo1kC7tWxEmS"
    }
  ]
}

Each key has a key identifier (kid). A signed token carries the identifier of the key that signed it, so a verifier can pick the right key from the set without trying each one. The set contains only public keys; a private key never belongs in it.

The key identifier helps a verifier choose among keys it already trusts. It never adds a key to that list. An app should fetch the set from the issuer address it was configured with, not from a location named inside the token it is checking, and should not accept a key embedded in the token. Apps usually cache the set and fetch it again when they see an identifier they do not recognize, with a limit on how often, so a flood of made-up identifiers cannot turn into a flood of requests.

Replacing a key

Keys are replaced on a schedule, when staff with access leave, when algorithms are upgraded, and after a suspected leak. A planned replacement, often called rotation, avoids breaking anything by overlapping the old and new keys:

  1. Generate the new key and add its public key to the published set.
  2. Wait until verifiers have had time to refresh their cached copies.
  3. Start signing new tokens with the new key.
  4. Keep the old public key published until every token it signed has expired.
  5. Remove the old public key and retire the private key.

In the example set above, clinic-2026-09 is the new key and clinic-2026-06 is still published for tokens signed before the switch. Publishing first means no app sees a token signed by a key it cannot find. Keeping the old key until its tokens expire means no valid token is rejected.

A leaked key is different. The clinic removes it from the published set immediately and accepts that tokens it signed will stop working, because some of those tokens may be forgeries. Having practiced planned rotation makes that emergency much less disruptive. The Identity security lessons on credential and key rotation cover scheduling, automation, and incident response in more depth.

A published key set has its own trust question. The apps trust it because they fetched it over HTTPS from the clinic's address, and HTTPS relies on the clinic's certificate. What a certificate says starts there.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2The clinic's signing key is non-exportable in a hardware security module, and an attacker takes over the application server. What changes compared with a key in a file?

QUESTION 2 OF 2The clinic replaces its token-signing key. Which order avoids rejecting valid tokens?

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

Learn identity