mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-03 21:02:15 +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]>
238 lines
9.6 KiB
Plaintext
238 lines
9.6 KiB
Plaintext
---
|
|
title: Configure private key JWT authentication for service users
|
|
sidebar_label: Private key JWT authentication
|
|
---
|
|
|
|
This guide explains how developers can use private key JWT authentication to secure communication between service users and client applications in ZITADEL.
|
|
|
|
In ZITADEL, the `urn:ietf:params:oauth:grant-type:jwt-bearer` (**"JWT bearer token with private key"**, [RFC7523](https://tools.ietf.org/html/rfc7523)) authorization grant type is used for non-interactive authentication.
|
|
|
|
Read more about the [different authentication methods for service users](./authenticate-service-users), including their benefits, drawbacks, and security considerations.
|
|
|
|
#### How private key JWT authentication works
|
|
|
|
1. Generate a private/public key pair associated with the service user.
|
|
2. The authorization server stores the public key.
|
|
3. The authorization server returns the private key as a JSON file.
|
|
4. The developer configures the client application to use the private key.
|
|
5. The client creates a JWT assertion with the subject set to the service user and signs the JWT with the private key.
|
|
6. The client application requests a token from ZITADEL by sending the `client_assertion`.
|
|
7. The authorization server validates the signature using the service user's public key.
|
|
8. The authorization server returns an OAuth `access_token`.
|
|
9. The client sends an API request, including the `access_token` in the Authorization header.
|
|
10. The resource server validates the JWT with [token introspection](../token-introspection/).
|
|
|
|

|
|
|
|
## Prerequisites
|
|
|
|
A code library or framework that supports JWT generation and verification (such as `pyjwt` for Python or `jsonwebtoken` for Node.js).
|
|
|
|
## Steps to authenticate a Service User with private JWT
|
|
|
|
Follow these steps to authenticate a service user and obtain an access token for subsequent requests.
|
|
|
|
### 1. Create a Service User
|
|
|
|
1. Navigate to **Service Users**.
|
|
2. Click on **New**.
|
|
3. Enter a username and a display name.
|
|
4. Click on **Create**.
|
|
|
|
### 2. Register a private key for your service user
|
|
|
|
You can let ZITADEL generate a private key for you, **or** upload your own (externally generated) public key. Choose one of the following:
|
|
|
|
#### Option A: Generate and download a private key from ZITADEL
|
|
|
|
1. Open the ZITADEL web console and go to your service user.
|
|
2. Click on the **Keys** tab.
|
|
3. Click **New**.
|
|
4. Optionally set an expiration date for the key, or leave empty for no expiration.
|
|
5. Click **Download** and save the JSON key file securely.
|
|
|
|
#### Option B: Register an existing public key for your service user
|
|
|
|
If you already have a key pair, you can upload the public key for your service user. For example, to generate a key pair using OpenSSL:
|
|
|
|
```bash
|
|
# generate key pair
|
|
openssl genrsa -out privatekey.pem 2048
|
|
|
|
# extract public key
|
|
openssl rsa -in privatekey.pem -pubout -out publickey.pem
|
|
```
|
|
|
|
To upload your public key, use the [User Service Add Key API](/reference/api/user/zitadel.user.v2.UserService.AddKey)
|
|
|
|
<Callout>
|
|
If you generate a key in ZITADEL, make sure to download and securely store the key file right away. For security reasons, ZITADEL cannot show you the private key again after creation. If the key file is lost, you must generate and register a new one.
|
|
</Callout>
|
|
|
|
<Callout title="Expiration">
|
|
If you set an expiration date when generating a key in ZITADEL, that key will expire at midnight on the specified day.
|
|
</Callout>
|
|
|
|

|
|
|
|
If you let ZITADEL generate a key for you and download the JSON file, it will look similar to the example below. In this ZITADEL-generated key file, the `key` property contains the _private_ key for your service account. Store this JSON securely and never share your private key. ZITADEL automatically stores the public key component, so you do not need to upload it.
|
|
|
|
```json
|
|
{
|
|
"type": "serviceaccount",
|
|
"keyId": "100509901696068329",
|
|
"key": "-----BEGIN RSA PRIVATE KEY----- [...] -----END RSA PRIVATE KEY-----\n",
|
|
"userId": "100507859606888466"
|
|
}
|
|
```
|
|
|
|
### 3. Create a JWT and sign it with your private key
|
|
|
|
You need to create a JWT with the following header and payload and sign it using the RS256 algorithm.
|
|
|
|
Header:
|
|
|
|
```json
|
|
{
|
|
"alg": "RS256",
|
|
"kid": "100509901696068329"
|
|
}
|
|
```
|
|
|
|
Make sure to include the `kid` header with the value of `keyId` from the downloaded JSON.
|
|
|
|
Payload:
|
|
|
|
```json
|
|
{
|
|
"iss": "100507859606888466",
|
|
"sub": "100507859606888466",
|
|
"aud": "https://$CUSTOM-DOMAIN",
|
|
"iat": [Current UTC timestamp, e.g. 1605179982, must be no older than 1 hour],
|
|
"exp": [Expiration UTC timestamp, e.g. 1605183582]
|
|
}
|
|
```
|
|
|
|
* `iss`: The entity making the request, i.e., the owner of the private key. Use the `userId` from your downloaded JSON.
|
|
* `sub`: The application. Set this to the same value as `userId`.
|
|
* `aud`: Your [Custom Domain](../../../concepts/features/custom-domain).
|
|
* `iat`: The Unix timestamp when the JWT is created/signed (must be no older than 1 hour).
|
|
* `exp`: The Unix timestamp when this assertion expires.
|
|
|
|
For further details, see the [JWT with private key](/apis/openidoauth/authn-methods#jwt-with-private-key) API reference.
|
|
|
|
If you use Go, you can use the [provided tool](https://github.com/zitadel/zitadel-tools) to generate a JWT from the downloaded JSON.
|
|
There are also [many libraries](https://jwt.io/#libraries-io) available for generating and signing JWTs.
|
|
|
|
**Code Example (Python using `pyjwt`):**
|
|
|
|
```python
|
|
import jwt
|
|
import datetime
|
|
|
|
# Replace with your service user ID and private key
|
|
service_user_id = "your_service_user_id"
|
|
private_key = "-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY\n-----END PRIVATE KEY-----"
|
|
key_id = "your_key_id"
|
|
|
|
# ZITADEL API URL (replace if needed)
|
|
api_url = "your_custom_domain"
|
|
|
|
# Generate JWT claims
|
|
payload = {
|
|
"iss": service_user_id,
|
|
"sub": service_user_id,
|
|
"aud": api_url,
|
|
"exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=5),
|
|
"iat": datetime.datetime.now(datetime.timezone.utc)
|
|
}
|
|
|
|
header = {
|
|
"alg": "RS256",
|
|
"kid": key_id
|
|
}
|
|
|
|
# Sign the JWT using the RS256 algorithm
|
|
encoded_jwt = jwt.encode(payload, private_key, algorithm="RS256", headers=header)
|
|
|
|
print(f"Generated JWT: {encoded_jwt}")
|
|
```
|
|
|
|
### 4. Request an OAuth token with the generated JWT
|
|
|
|
With the encoded JWT from the previous step, craft a POST request to ZITADEL's token endpoint:
|
|
|
|
```bash
|
|
curl --request POST \
|
|
--url https:/$CUSTOM-DOMAIN/oauth/v2/token \
|
|
--header 'Content-Type: application/x-www-form-urlencoded' \
|
|
--data grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
|
|
--data scope='openid' \
|
|
--data assertion=eyJ0eXAiOiJKV1QiL...
|
|
```
|
|
|
|
* `grant_type`: Must be set to `urn:ietf:params:oauth:grant-type:jwt-bearer`
|
|
* `scope`: Include any desired [Scopes](/apis/openidoauth/scopes), but must include `openid`.
|
|
* `assertion`: The encoded JWT you created and signed with your private key.
|
|
|
|
If you want to access ZITADEL APIs, make sure to include the required scope `urn:zitadel:iam:org:project:id:zitadel:aud`.
|
|
See our guide on [how to access ZITADEL APIs](../zitadel-apis/access-zitadel-apis) for more details.
|
|
|
|
**Important Note:** If the service user token must be validated using token introspection, you must include the `urn:zitadel:iam:org:project:id:{projectid}:aud` scope in your token request.
|
|
If you do not include this, token introspection will fail.
|
|
|
|
A successful response will include the `access_token`, `token_type`, and the time to expiry in seconds (`expires_in`):
|
|
|
|
```bash
|
|
HTTP/1.1 200 OK
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"access_token": "MtjHodGy4zxKylDOhg6kW90WeEQs2q...",
|
|
"token_type": "Bearer",
|
|
"expires_in": 43199
|
|
}
|
|
```
|
|
|
|
### 5. Include the access token in the Authorization header
|
|
|
|
When making API requests as the service user, include the generated token in the "Authorization" header with the "Bearer" prefix.
|
|
|
|
```bash
|
|
curl --request POST \
|
|
--url $YOUR_API_ENDPOINT \
|
|
--header 'Content-Type: application/x-www-form-urlencoded' \
|
|
--header 'Authorization: Bearer MtjHodGy4zxKylDOhg6kW90WeEQs2q...'
|
|
```
|
|
|
|
## Accessing ZITADEL APIs
|
|
|
|
You may use ZITADEL APIs to manage resources (e.g., users) or to validate tokens received by your backend service.
|
|
Follow our guide on [how to access ZITADEL API](../zitadel-apis/access-zitadel-apis) with your service user.
|
|
|
|
### Token introspection
|
|
|
|
If your API endpoint receives tokens from users and needs to validate them with ZITADEL,
|
|
your API must first authenticate with ZITADEL, then perform a token introspection.
|
|
See our [guide on token introspection with private key JWT](../token-introspection/private-key-jwt) for details.
|
|
|
|
## Client application authentication
|
|
|
|
The steps above describe service user authentication.
|
|
If your application itself also needs authentication, you can use the [Client Credentials Grant](./client-credentials).
|
|
See the ZITADEL documentation for additional details on this approach.
|
|
|
|
## Security considerations
|
|
|
|
* **Store private keys securely:** **Never share or embed the private key in your code or application.** Use secure key management solutions whenever possible.
|
|
* **Set appropriate JWT expiration times:** Limit token validity to reduce risk in the event of a compromise.
|
|
* **Implement proper error handling:** Handle cases where JWT verification fails or tokens are expired.
|
|
|
|
By following these steps and adhering to best practices, you can securely manage service user and client application authentication within ZITADEL using private key JWTs.
|
|
|
|
## Notes
|
|
|
|
* [JWT with private key](/apis/openidoauth/authn-methods#jwt-with-private-key) API reference
|
|
* [Accessing ZITADEL API](../zitadel-apis/access-zitadel-apis)
|
|
* [Token introspection with private key JWT](../token-introspection/private-key-jwt)
|