mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-28 01:52:16 +00:00
docs: update private key jwt for token instrospection (#11227)
The current document for private key JWT is misleading, as it currently looks like a mechanism to secure an API, and it's not, this section only shows how to use private key JWT to call the introspect endpoint
This commit is contained in:
@@ -1,193 +1,200 @@
|
||||
---
|
||||
title: JSON Web Token Profile in ZITADEL
|
||||
sidebar_label: JSON Web Token(JWT) Profile
|
||||
title: Private Key JWT
|
||||
sidebar_label: Private Key JWT
|
||||
---
|
||||
|
||||
import IntrospectionResponse from './_introspection-response.mdx';
|
||||
|
||||
This is a guide on how to secure your API using [JSON Web Token (JWT) profile (recommended)](/docs/apis/openidoauth/authn-methods#client-secret-basic).
|
||||
This guide explains how to use ZITADEL's [Private Key JWT authentication method](/docs/apis/openidoauth/authn-methods#jwt-with-private-key) to authenticate your application and call the token introspection endpoint. This is not about securing an API, but about allowing your backend to introspect access tokens in a secure way.
|
||||
|
||||
## Register the API in ZITADEL and generate private and public keys
|
||||
If you're looking to set up Private Key JWT authentication specifically for Service Users, see our dedicated guide [here](/docs/guides/integrate/service-users/private-key-jwt).
|
||||
|
||||
1. Go to your project and click on the **New** button as shown below.
|
||||
## Register an Application & Generate a Key Pair
|
||||
|
||||
To use Private Key JWT for introspection, you must register your client application in ZITADEL and create a key pair. This process enables your application to obtain credentials needed to generate JWT assertions for authentication.
|
||||
|
||||
1. In your ZITADEL project, click **New** to add an application.
|
||||
|
||||
<img
|
||||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-1.png"
|
||||
width="75%"
|
||||
alt="Register the API"
|
||||
alt="Register the app"
|
||||
/>
|
||||
|
||||
2. Give a name to your application (Test API is the name given below) and select type **API**.
|
||||
2. Enter a name (e.g., "Backend Client") and select **API** as the application type.
|
||||
|
||||
<img
|
||||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-2.png"
|
||||
width="75%"
|
||||
alt="Register the API"
|
||||
alt="Set application type"
|
||||
/>
|
||||
|
||||
3. Select **JWT** as the authentication method and click **Continue**.
|
||||
3. Choose **JWT** as the authentication method and click **Continue**.
|
||||
|
||||
<img
|
||||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-3.png"
|
||||
width="75%"
|
||||
alt="Register the API"
|
||||
alt="Choose JWT Profile"
|
||||
/>
|
||||
|
||||
4. Now review your configuration and click **Create**.
|
||||
4. Review your settings and click **Create**.
|
||||
|
||||
<img
|
||||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-4.png"
|
||||
width="75%"
|
||||
alt="Register the API"
|
||||
alt="Create application"
|
||||
/>
|
||||
|
||||
5. You will now see the API’s **Client ID**. You will not see a client secret because we are using a private JWT key.
|
||||
5. After creation, you’ll see the application’s **Client ID**. There is no client secret—instead, authentication relies on your private key and JWT.
|
||||
|
||||
<img
|
||||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-5.png"
|
||||
width="75%"
|
||||
alt="Register the API"
|
||||
alt="Client ID"
|
||||
/>
|
||||
|
||||
6. Next, we must create the key pairs. Click on **New**.
|
||||
6. To generate a key pair, click **New** under the application’s keys section.
|
||||
|
||||
<img
|
||||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-6.png"
|
||||
width="75%"
|
||||
alt="Register the API"
|
||||
alt="Generate a new key"
|
||||
/>
|
||||
|
||||
7. Select **JSON** as the type of key. You can also set an expiration time for the key or leave it empty. Click on **Add**.
|
||||
7. Select **JSON** as the key type, set an expiration if desired, and click **Add**.
|
||||
|
||||
<img
|
||||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-7.png"
|
||||
width="75%"
|
||||
alt="Register the API"
|
||||
alt="Key details"
|
||||
/>
|
||||
|
||||
8. Download the created key by clicking the **Download** button and then click **Close**.
|
||||
8. Download and save the generated key by clicking **Download**. Afterward, click **Close**.
|
||||
|
||||
<img
|
||||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-8.png"
|
||||
width="75%"
|
||||
alt="Register the API"
|
||||
alt="Download key"
|
||||
/>
|
||||
|
||||
9. The key will be downloaded.
|
||||
9. The downloaded key file will look like this:
|
||||
|
||||
<img
|
||||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-9.png"
|
||||
width="75%"
|
||||
alt="Register the API"
|
||||
/>
|
||||
```
|
||||
{
|
||||
"type": "application",
|
||||
"keyId": "<YOUR_KEY_ID>",
|
||||
"key": "-----BEGIN RSA PRIVATE KEY-----\n<YOUR_PRIVATE_KEY>\n-----END RSA PRIVATE KEY-----\n",
|
||||
"appId": "<YOUR_APP_ID>",
|
||||
"clientId": "<YOUR_CLIENT_ID>"
|
||||
}
|
||||
```
|
||||
|
||||
10. When you click on URLs on the left, you will see the relevant OIDC URLs. Note down the **issuer** URL, **token_endpoint** and **introspection_endpoint**.
|
||||
10. In the left menu, select **URLs** to view the application’s OIDC endpoints. Note the **issuer** URL, **token_endpoint**, and **introspection_endpoint**.
|
||||
|
||||
<img
|
||||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-10.png"
|
||||
width="75%"
|
||||
alt="Register the API"
|
||||
alt="OIDC Endpoints"
|
||||
/>
|
||||
|
||||
11. The key that you downloaded will be of the following format.
|
||||
```
|
||||
{
|
||||
"type":"application",
|
||||
"keyId":"<YOUR_KEY_ID>",
|
||||
"key":"-----BEGIN RSA PRIVATE KEY-----\<YOUR_PRIVATE_KEY>\n-----END RSA PRIVATE KEY-----\n",
|
||||
"appId":"<YOUR_APP_ID>",
|
||||
"clientId":"<YOUR_CLIENT_ID>"
|
||||
}
|
||||
```
|
||||
12. Also note down the **Resource ID** of your project.
|
||||
11. Optionally, note your **Resource ID** for reference.
|
||||
|
||||
<img
|
||||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-11.png"
|
||||
width="75%"
|
||||
alt="Register the API"
|
||||
alt="Resource ID"
|
||||
/>
|
||||
|
||||
## Token introspection
|
||||
## Calling the Introspection Endpoint
|
||||
|
||||
You must send a client_assertion as a JWT signed with the API’s private key for ZITADEL to validate the signature against the registered public key.
|
||||
To introspect an access token in ZITADEL, your backend must authenticate by providing a JWT (`client_assertion`) signed with your application's private key. ZITADEL verifies this JWT to ensure the request is from a trusted application.
|
||||
|
||||
Request parameters:
|
||||
**Required request parameters:**
|
||||
|
||||
| **Parameter** | **Description** |
|
||||
---|---
|
||||
| `client_assertion` | When using JWT profile for token or introspection endpoints, you must provide a JWT as an assertion generated with the structure shown below and signed with the downloaded key. |
|
||||
| `client_assertion_type` | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
|
||||
| **Parameter** | **Description** |
|
||||
|------------------------- |---------------------------------------------------------------------------------------------------------------------|
|
||||
| `client_assertion` | The JWT assertion created and signed as described below. |
|
||||
| `client_assertion_type` | Must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
|
||||
| `token` | The access token you want to introspect. |
|
||||
|
||||
### Creating the Client Assertion (JWT)
|
||||
|
||||
You must create your client assertion JWT with the following format:
|
||||
The `client_assertion` parameter is a signed JWT with the following structure:
|
||||
|
||||
Header:
|
||||
**Header:**
|
||||
```json
|
||||
{
|
||||
"alg": "RS256",
|
||||
"kid": "81693565968962154" (keyId from your key file)
|
||||
"alg": "RS256",
|
||||
"kid": "<YOUR_KEY_ID>"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Payload:
|
||||
**Payload:**
|
||||
```json
|
||||
{
|
||||
"iss": "78366401571920522@acme", (clientId from your key file)
|
||||
"sub": "78366401571920522@acme", (clientId from your key file)
|
||||
"aud": "https://${CUSTOM_DOMAIN}", (your ZITADEL domain/issuer URL)
|
||||
"exp": 1605183582, (Unix timestamp of the expiry)
|
||||
"iat": 1605179982 (Unix timestamp of the creation signing time of the JWT, MUST NOT be older than 1h)
|
||||
"iss": "<YOUR_CLIENT_ID>",
|
||||
"sub": "<YOUR_CLIENT_ID>",
|
||||
"aud": "https://${YOUR_DOMAIN}", // Your ZITADEL issuer URL
|
||||
"exp": 1605183582, // Expiry (Unix timestamp)
|
||||
"iat": 1605179982 // Issued at (Unix timestamp, not older than 1 hour)
|
||||
}
|
||||
```
|
||||
|
||||
Create the JSON Web Token with the above header and payload, and sign it with the private key in your key file. You can do this programmatically or use tools like [https://github.com/zitadel/zitadel-tools](https://github.com/zitadel/zitadel-tools) and [https://dinochiesa.github.io/jwt/](https://dinochiesa.github.io/jwt/).
|
||||
- `iss` and `sub`: Your application’s Client ID.
|
||||
- `aud`: The issuer URL for your ZITADEL instance (e.g. `https://my-tenant.zitadel.cloud`).
|
||||
- `exp`: JWT expiration time (seconds since epoch).
|
||||
- `iat`: JWT issued at time (seconds since epoch), must not be older than 1 hour.
|
||||
|
||||
You can generate the signed JWT using libraries in your preferred language or helper tools like [zitadel-tools](https://github.com/zitadel/zitadel-tools) or [https://dinochiesa.github.io/jwt/](https://dinochiesa.github.io/jwt/).
|
||||
|
||||
### Example Introspection HTTP Request
|
||||
|
||||
Make a POST request to the introspection endpoint, sending the above parameters:
|
||||
|
||||
The request from the API to the introspection endpoint should be in the following format:
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url ${CUSTOM_DOMAIN}/oauth/v2/introspect \
|
||||
--header 'Content-Type: application/x-www-form-urlencoded' \
|
||||
--data client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
|
||||
--data client_assertion=eyJhbGciOiJSUzI1Ni... \
|
||||
--data token=VjVxyCZmRmWYqd3_F5db9Pb9mHR
|
||||
```
|
||||
--url ${YOUR_DOMAIN}/oauth/v2/introspect \
|
||||
--header 'Content-Type: application/x-www-form-urlencoded' \
|
||||
--data client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
|
||||
--data client_assertion=eyJhbGciOiJSUzI1Ni... \
|
||||
--data token=VjVxyCZmRmWYqd3_F5db9Pb9mHR
|
||||
```
|
||||
|
||||
Here's an example of how this is done in Python code:
|
||||
### Python Example
|
||||
|
||||
Below is a Python function that generates the JWT assertion and performs token introspection:
|
||||
|
||||
```python
|
||||
def introspect_token(self, token_string):
|
||||
#Create JWT for client assertion
|
||||
def introspect_token(token_string):
|
||||
# Build JWT for client assertion
|
||||
payload = {
|
||||
"iss": API_PRIVATE_KEY_FILE["client_id"],
|
||||
"sub": API_PRIVATE_KEY_FILE["client_id"],
|
||||
"aud": CUSTOM_DOMAIN,
|
||||
"exp": int(time.time()) + 60 * 60, # Expires in 1 hour
|
||||
"iss": API_KEY_FILE["clientId"],
|
||||
"sub": API_KEY_FILE["clientId"],
|
||||
"aud": ZITADEL_ISSUER_URL,
|
||||
"exp": int(time.time()) + 60 * 60, # expires in 1 hour
|
||||
"iat": int(time.time())
|
||||
}
|
||||
headers = {
|
||||
"alg": "RS256",
|
||||
"kid": API_PRIVATE_KEY_FILE["key_id"]
|
||||
"kid": API_KEY_FILE["keyId"]
|
||||
}
|
||||
jwt_token = jwt.encode(payload, API_PRIVATE_KEY_FILE["private_key"], algorithm="RS256", headers=headers)
|
||||
jwt_token = jwt.encode(payload, API_KEY_FILE["key"], algorithm="RS256", headers=headers)
|
||||
|
||||
#Send introspection request
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
data = {
|
||||
req_headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
req_data = {
|
||||
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
"client_assertion": jwt_token,
|
||||
"token": token_string
|
||||
}
|
||||
response = requests.post(ZITADEL_INTROSPECTION_URL, headers=headers, data=data)
|
||||
response = requests.post(ZITADEL_INTROSPECTION_ENDPOINT, headers=req_headers, data=req_data)
|
||||
response.raise_for_status()
|
||||
token_data = response.json()
|
||||
print(f"Token data from introspection: {token_data}")
|
||||
return token_data
|
||||
|
||||
```
|
||||
## Introspection response
|
||||
|
||||
## Introspection Response
|
||||
|
||||
<IntrospectionResponse/>
|
||||
|
||||
Follow this [tutorial](https://github.com/zitadel/examples-api-access-and-token-introspection/tree/main/api-jwt) to learn how to register an API application using JWT Profile with ZITADEL and test it.
|
||||
|
||||
For more details and a complete working example, see this [tutorial](https://github.com/zitadel/examples-api-access-and-token-introspection/tree/main/api-jwt) on registering an application in ZITADEL and calling the introspection endpoint with a JWT assertion.
|
||||
|
||||
Reference in New Issue
Block a user