diff --git a/docs/docs/guides/integrate/token-introspection/_introspection-response.mdx b/docs/docs/guides/integrate/token-introspection/_introspection-response.mdx
new file mode 100644
index 0000000000..f3e7b20f4e
--- /dev/null
+++ b/docs/docs/guides/integrate/token-introspection/_introspection-response.mdx
@@ -0,0 +1,21 @@
+Upon successful introspection, regardless of the token type or introspection method, a response with the boolean `active` is returned, indicating if the provided token is active and if the requesting client is part of the token audience. If `active` is true, further information will be provided:
+
+| **Property** | **Description** |
+| --- | --- |
+| `aud` | The audience of the token |
+| `client_id` | The client_id of the application the token was issued to |
+| `exp` | Time the token expires (as unix time) |
+| `iat` | Time the token was issued at (as unix time) |
+| `iss` | Issuer of the token |
+| `jti` | Unique id of the token |
+| `nbf` | Time the token must not be used before (as unix time) |
+| `scope` | Space delimited list of scopes granted to the token |
+| `token_type` | Type of the inspected token. Value is always Bearer |
+| `username` | ZITADEL's login name of the user. Consists of username@primarydomain |
+
+
+Depending on the granted scopes, additional information about the authorized user is provided.
+
+If the authorization fails, an HTTP 401 with invalid_client will be returned.
+
+In summary, the introspection endpoint plays a crucial role in validating access tokens, either opaque or JWT, ensuring that they are not revoked.
diff --git a/docs/docs/guides/integrate/token-introspection/basic-auth.mdx b/docs/docs/guides/integrate/token-introspection/basic-auth.mdx
new file mode 100644
index 0000000000..18f730de5b
--- /dev/null
+++ b/docs/docs/guides/integrate/token-introspection/basic-auth.mdx
@@ -0,0 +1,103 @@
+---
+title: Basic authentication
+---
+
+import IntrospectionResponse from './_introspection-response.mdx';
+
+This is a guide on how to secure your API using [Basic Authentication](https://zitadel.com/docs/apis/openidoauth/authn-methods#client-secret-basic).
+
+## Register the API in ZITADEL
+
+1. Go to your project and click on the **New** button as shown below.
+
+
+
+2. Give a name to your application (Test API 2 is the name given below) and select type **API**.
+
+
+
+3. Select **Basic** as the authentication method and click **Continue**.
+
+
+
+4. Now review your configuration and click **Create**.
+
+
+
+5. You will now see the API’s **Client ID** and the **Client Secret**. Copy them and click **Close**.
+
+
+
+6. When you click **URLs** on the left, you will see the relevant OIDC URLs. Note down the **issuer** URL, **token_endpoint** and **introspection_endpoint**.
+
+
+
+7. Also note down the **Resource ID** of your project.
+
+
+
+## Token introspection
+
+With Basic Authentication, you will receive a Client ID and Client Secret for your API. Send your client_id and client_secret as a Basic Auth Header in the following format:
+
+```
+Authorization: "Basic " + base64( formUrlEncode(client_id) + ":" + formUrlEncode(client_secret) )
+
+```
+
+The request from the API to the introspection endpoint should be in the following format:
+
+```
+curl --request POST \
+ --url {your_domain}/oauth/v2/introspect \
+ --header 'Content-Type: application/x-www-form-urlencoded' \
+ --header 'Authorization: Basic {your_basic_auth_header}' \
+ --data token=VjVxyCZmRmWYqd3_F5db9Pb9mHR5fqzhn...
+ ```
+
+Here's an example of how this is done in Python code:
+
+```
+def introspect_token(self, token_string):
+ url = ZITADEL_INTROSPECTION_URL
+ data = {'token': token_string, 'token_type_hint': 'access_token', 'scope': 'openid'}
+ auth = HTTPBasicAuth(API_CLIENT_ID, API_CLIENT_SECRET)
+ resp = requests.post(url, data=data, auth=auth)
+ resp.raise_for_status()
+ return resp.json()
+```
+
+## Introspection response
+
+
+
+Follow this [tutorial](https://github.com/zitadel/examples-api-access-and-token-introspection/tree/main/api-basic-authentication) to learn how to register an API application using Basic Auth with ZITADEL and test it.
+
diff --git a/docs/docs/guides/integrate/token-introspection/private-key-jwt.mdx b/docs/docs/guides/integrate/token-introspection/private-key-jwt.mdx
new file mode 100644
index 0000000000..3c83f7cc16
--- /dev/null
+++ b/docs/docs/guides/integrate/token-introspection/private-key-jwt.mdx
@@ -0,0 +1,192 @@
+---
+title: JSON Web Token profile
+---
+
+import IntrospectionResponse from './_introspection-response.mdx';
+
+This is a guide on how to secure your API using [JSON Web Token (JWT) profile (recommended)](https://zitadel.com/docs/apis/openidoauth/authn-methods#client-secret-basic).
+
+## Register the API in ZITADEL and generate private and public keys
+
+1. Go to your project and click on the **New** button as shown below.
+
+
+
+2. Give a name to your application (Test API is the name given below) and select type **API**.
+
+
+
+3. Select **JWT** as the authentication method and click **Continue**.
+
+
+
+4. Now review your configuration and click **Create**.
+
+
+
+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.
+
+
+
+6. Next, we must create the key pairs. Click on **New**.
+
+
+
+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**.
+
+
+
+8. Download the created key by clicking the **Download** button and then click **Close**.
+
+
+
+9. The key will be downloaded.
+
+
+
+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**.
+
+
+
+11. The key that you downloaded will be of the following format.
+```
+{
+ "type":"application",
+ "keyId":"",
+ "key":"-----BEGIN RSA PRIVATE KEY-----\\n-----END RSA PRIVATE KEY-----\n",
+ "appId":"",
+ "clientId":""
+}
+```
+12. Also note down the **Resource ID** of your project.
+
+
+
+## Token introspection
+
+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.
+
+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` |
+
+
+You must create your client assertion JWT with the following format:
+
+Header:
+```json
+{
+ "alg": "RS256",
+ "kid": "81693565968962154" (keyId from your key file)
+}
+```
+
+
+Payload:
+```json
+{
+ "iss": "78366401571920522@acme", (clientId from your key file)
+ "sub": "78366401571920522@acme", (clientId from your key file)
+ "aud": "https://{your_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)
+}
+```
+
+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/).
+
+The request from the API to the introspection endpoint should be in the following format:
+```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
+ ```
+
+Here's an example of how this is done in Python code:
+
+```python
+def introspect_token(self, token_string):
+ #Create JWT for client assertion
+ payload = {
+ "iss": API_PRIVATE_KEY_FILE["client_id"],
+ "sub": API_PRIVATE_KEY_FILE["client_id"],
+ "aud": ZITADEL_DOMAIN,
+ "exp": int(time.time()) + 60 * 60, # Expires in 1 hour
+ "iat": int(time.time())
+ }
+ headers = {
+ "alg": "RS256",
+ "kid": API_PRIVATE_KEY_FILE["key_id"]
+ }
+ jwt_token = jwt.encode(payload, API_PRIVATE_KEY_FILE["private_key"], algorithm="RS256", headers=headers)
+
+ #Send introspection request
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+ 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.raise_for_status()
+ token_data = response.json()
+ print(f"Token data from introspection: {token_data}")
+ return token_data
+
+```
+## Introspection response
+
+
+
+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.
+
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 2be4988565..c85ea1ca64 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -150,6 +150,22 @@ module.exports = {
"guides/integrate/logout",
],
},
+ {
+ type: "category",
+ label: "Token introspection",
+ link: {
+ type: "generated-index",
+ title: "Token introspection",
+ slug: "/guides/integrate/token-introspection",
+ description:
+ "Token introspection is the process of checking whether an access token is valid and can be used to access protected resources. You have an API that acts as an OAuth resource server and can be accessed by user-facing applications. To validate an access token by calling the ZITADEL introspection API, you can use the JSON Web Token (JWT) Profile (recommended) or Basic Authentication for token introspection. It's crucial to understand that the API is entirely separate from the front end. The API shouldn’t concern itself with the token type received. Instead, it's about how the API chooses to call the introspection endpoint, either through JWT Profile or Basic Authentication. Many APIs assume they might receive a JWT and attempt to verify it based on signature or expiration. However, with ZITADEL, you can send either a JWT or an opaque Bearer token from the client end to the API. This flexibility is one of ZITADEL's standout features.",
+ },
+ collapsed: true,
+ items: [
+ "guides/integrate/token-introspection/private-key-jwt",
+ "guides/integrate/token-introspection/basic-auth",
+ ],
+ },
{
type: "category",
label: "Authenticate service users",
@@ -169,13 +185,13 @@ module.exports = {
},
{
type: "category",
- label: "Build your own Login-UI",
+ label: "Build your own login UI",
link: {
type: "generated-index",
- title: "Build your own Login-UI",
+ title: "Build your own login UI",
slug: "/guides/integrate/login-ui",
description:
- "In the following guides you will learn how to create your own login ui with our APIs. The different scenarios like username/password, external identity provider, etc will be shown.",
+ "In the following guides you will learn how to create your own login UI with our APIs. The different scenarios like username/password, external identity provider, etc. will be shown."
},
collapsed: true,
diff --git a/docs/static/img/guides/integrate/token-introspection-basic-auth-1.png b/docs/static/img/guides/integrate/token-introspection-basic-auth-1.png
new file mode 100644
index 0000000000..38c25a4b16
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-basic-auth-1.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-basic-auth-2.png b/docs/static/img/guides/integrate/token-introspection-basic-auth-2.png
new file mode 100644
index 0000000000..6915f4b8f7
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-basic-auth-2.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-basic-auth-3.png b/docs/static/img/guides/integrate/token-introspection-basic-auth-3.png
new file mode 100644
index 0000000000..a0a18f2069
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-basic-auth-3.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-basic-auth-4.png b/docs/static/img/guides/integrate/token-introspection-basic-auth-4.png
new file mode 100644
index 0000000000..ac3fa06d2d
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-basic-auth-4.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-basic-auth-5.png b/docs/static/img/guides/integrate/token-introspection-basic-auth-5.png
new file mode 100644
index 0000000000..a6dbe546a5
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-basic-auth-5.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-basic-auth-6.png b/docs/static/img/guides/integrate/token-introspection-basic-auth-6.png
new file mode 100644
index 0000000000..05c58384b1
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-basic-auth-6.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-basic-auth-7.png b/docs/static/img/guides/integrate/token-introspection-basic-auth-7.png
new file mode 100644
index 0000000000..c3b9843832
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-basic-auth-7.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-jwt-profile-1.png b/docs/static/img/guides/integrate/token-introspection-jwt-profile-1.png
new file mode 100644
index 0000000000..1025eb869e
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-jwt-profile-1.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-jwt-profile-10.png b/docs/static/img/guides/integrate/token-introspection-jwt-profile-10.png
new file mode 100644
index 0000000000..05c58384b1
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-jwt-profile-10.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-jwt-profile-11.png b/docs/static/img/guides/integrate/token-introspection-jwt-profile-11.png
new file mode 100644
index 0000000000..c3b9843832
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-jwt-profile-11.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-jwt-profile-2.png b/docs/static/img/guides/integrate/token-introspection-jwt-profile-2.png
new file mode 100644
index 0000000000..1bb5927636
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-jwt-profile-2.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-jwt-profile-3.png b/docs/static/img/guides/integrate/token-introspection-jwt-profile-3.png
new file mode 100644
index 0000000000..d4deeb6e9d
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-jwt-profile-3.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-jwt-profile-4.png b/docs/static/img/guides/integrate/token-introspection-jwt-profile-4.png
new file mode 100644
index 0000000000..7063c251ea
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-jwt-profile-4.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-jwt-profile-5.png b/docs/static/img/guides/integrate/token-introspection-jwt-profile-5.png
new file mode 100644
index 0000000000..9f5bddb7db
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-jwt-profile-5.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-jwt-profile-6.png b/docs/static/img/guides/integrate/token-introspection-jwt-profile-6.png
new file mode 100644
index 0000000000..d213eaee94
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-jwt-profile-6.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-jwt-profile-7.png b/docs/static/img/guides/integrate/token-introspection-jwt-profile-7.png
new file mode 100644
index 0000000000..d783c92cd7
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-jwt-profile-7.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-jwt-profile-8.png b/docs/static/img/guides/integrate/token-introspection-jwt-profile-8.png
new file mode 100644
index 0000000000..47299c0920
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-jwt-profile-8.png differ
diff --git a/docs/static/img/guides/integrate/token-introspection-jwt-profile-9.png b/docs/static/img/guides/integrate/token-introspection-jwt-profile-9.png
new file mode 100644
index 0000000000..e20a664493
Binary files /dev/null and b/docs/static/img/guides/integrate/token-introspection-jwt-profile-9.png differ