mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-08 07:05:12 +00:00
## Todos for release - [x] Configure Env in docs project on vercel - [x] Configure Root Path in the docs project on vercel - [ ] Remove old CSP https://github.com/zitadel/website/pull/1592 ## What we did This pull request migrates the project documentation from the old `docs/` directory to the new `apps/docs/` directory, introduces a new documentation system built with Next.js and Fumadocs, and updates all relevant references, configuration files, and documentation to reflect this change. It also adds new configuration and ignore files for the new documentation app, updates CI and linting to exclude the new docs from certain checks, and revises the contributing guidelines accordingly. **Documentation System Migration and New Docs App** * Migrated all documentation from `docs/` to `apps/docs/`, and updated all references in `README.md`, `CONTRIBUTING.md`, and other files to point to the new location. [[1]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L585-L640) [[2]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L660-R607) [[3]](diffhunk://#diff-eca12c0a30e25b4b46522ebf89465a03ba72a03f540796c979137931d8f92055L740-R687) [[4]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L2-R3) [[5]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L30-R30) * Added a new Next.js/Fumadocs-based documentation app under `apps/docs/`, including core app files, layouts, routing, search API, and a comprehensive `README.md` with development and contribution instructions. [[1]](diffhunk://#diff-5a1b07344a2c1b4d3f37b23ff1388b62cd9f57dea3c1cd23d8a0412b7602b132R1-R74) [[2]](diffhunk://#diff-462b9ad1eabbb7d1c29bb9c36e4931eb180fd7190900e6d2babf8f4d66ad1c28R1-R39) [[3]](diffhunk://#diff-e16ae25660ded787b10ac35dea96d5ecaacf895dae0afc8a9bd4382dc79a8c87R1-R7) [apps/docs/app/[[...slug]]/layout.tsxR1-R81](diffhunk://#diff-59e08acde4e805b7aeccef1dcf98f1d71dfc550777e6b9402085cee0e9fa4e0aR1-R81), [apps/docs/app/[[...slug]]/page.tsxR1-R79](diffhunk://#diff-e5df3f80d0fa01e12d63d81f29c57a9d14e846c78fd3beabb2ef768e38fd9580R1-R79), [[4]](diffhunk://#diff-389b34918e040cacaa87cd7201ffa462cc2b0b716736f537e3d3c660ac69353bR1-R7) [[5]](diffhunk://#diff-d3b03416d1c457b19f1c27b26f2db741412df841b875c26ccadc36e4522247f4R1-R29) [[6]](diffhunk://#diff-c8fb8339570a5305809be7c618e14705fd86390dc278e6ce8ba224a7bc8b0c3cR1-R25) **Configuration and Tooling Updates** * Updated `.github/workflows/codeql.yml`, `.golangci.yaml`, and `.github/dependabot.yml` to properly handle the new docs app: excluded `apps/docs` from certain checks, added npm dependency updates for the docs app, and excluded generated content. [[1]](diffhunk://#diff-12783128521e452af0cfac94b99b8d250413c516ec71fe6d97dbea666ff7ba27L8-R14) [[2]](diffhunk://#diff-9917ddc9f1c3304218f7269265b746d997c5c0615478177b5fceecd33ef47cb5R5-R6) [[3]](diffhunk://#diff-9917ddc9f1c3304218f7269265b746d997c5c0615478177b5fceecd33ef47cb5R126-R129) [[4]](diffhunk://#diff-dd4fbda47e51f1e35defb9275a9cd9c212ecde0b870cba89ddaaae65c5f3cd28R89-R106) * Updated `.devcontainer/devcontainer.json` to use the latest Go 1.25.3 version for consistency. **Licensing and Miscellaneous** * Added `apps/docs/` to the list of licensed directories in `LICENSING.md`. These changes ensure the documentation is now maintained in a modern, scalable system and all project tooling is updated to support the new structure. --------- Co-authored-by: Federico Coppede <[email protected]>
201 lines
7.1 KiB
Plaintext
201 lines
7.1 KiB
Plaintext
---
|
||
title: Private Key JWT
|
||
sidebar_label: Private Key JWT
|
||
---
|
||
|
||
import IntrospectionResponse from './_introspection-response.mdx';
|
||
|
||
This guide explains how to use ZITADEL's [Private Key JWT authentication method](/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.
|
||
|
||
If you're looking to set up Private Key JWT authentication specifically for Service Users, see our dedicated guide [here](/guides/integrate/service-users/private-key-jwt).
|
||
|
||
## 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 app"
|
||
/>
|
||
|
||
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="Set application type"
|
||
/>
|
||
|
||
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="Choose JWT Profile"
|
||
/>
|
||
|
||
4. Review your settings and click **Create**.
|
||
|
||
<img
|
||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-4.png"
|
||
width="75%"
|
||
alt="Create application"
|
||
/>
|
||
|
||
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="Client ID"
|
||
/>
|
||
|
||
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="Generate a new key"
|
||
/>
|
||
|
||
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="Key details"
|
||
/>
|
||
|
||
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="Download key"
|
||
/>
|
||
|
||
9. The downloaded key file will look like this:
|
||
|
||
```
|
||
{
|
||
"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. 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="OIDC Endpoints"
|
||
/>
|
||
|
||
11. Optionally, note your **Resource ID** for reference.
|
||
|
||
<img
|
||
src="/docs/img/guides/integrate/token-introspection-jwt-profile-11.png"
|
||
width="75%"
|
||
alt="Resource ID"
|
||
/>
|
||
|
||
## Calling the Introspection Endpoint
|
||
|
||
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.
|
||
|
||
**Required request parameters:**
|
||
|
||
| **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)
|
||
|
||
The `client_assertion` parameter is a signed JWT with the following structure:
|
||
|
||
**Header:**
|
||
```json
|
||
{
|
||
"alg": "RS256",
|
||
"kid": "<YOUR_KEY_ID>"
|
||
}
|
||
```
|
||
|
||
**Payload:**
|
||
```json
|
||
{
|
||
"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)
|
||
}
|
||
```
|
||
|
||
- `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:
|
||
|
||
```bash
|
||
curl --request POST \
|
||
--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
|
||
```
|
||
|
||
### Python Example
|
||
|
||
Below is a Python function that generates the JWT assertion and performs token introspection:
|
||
|
||
```python
|
||
def introspect_token(token_string):
|
||
# Build JWT for client assertion
|
||
payload = {
|
||
"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_KEY_FILE["keyId"]
|
||
}
|
||
jwt_token = jwt.encode(payload, API_KEY_FILE["key"], algorithm="RS256", headers=headers)
|
||
|
||
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_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
|
||
|
||
<IntrospectionResponse components={props.components} />
|
||
|
||
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.
|