docs: add pylon framework to examples (#8115)

# Which Problems Are Solved

- Lack of documentation on integrating the Pylon framework with ZITADEL

# How the Problems Are Solved

- Adds examples to the ZITADEL documentation on how to integrate with
the Pylon framework.
- Provides clear, step-by-step instructions and code snippets for
seamless integration.

# Additional Changes

- Updates some formatting related issues. This includes changes to
trailing semicolons and array newlines in two or three instances without
significantly altering the previous formatting.
5b23416a8c

# Additional Context

Add the pylon framework to the ZITADEL documentation examples as
previously discussed with @fforootd.

- [Pylon](https://github.com/getcronit/pylon)
- [Pylon Documentation](https://pylon.cronit.io)

---------

Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
This commit is contained in:
Nico Schett 2024-06-17 09:37:07 +02:00 committed by GitHub
parent 18222008b6
commit ca69ba41ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 332 additions and 18 deletions

View File

@ -0,0 +1 @@
You have to install Pylon as described in [their documentation](https://pylon.cronit.io/docs/installation/).

View File

@ -0,0 +1,304 @@
---
title: ZITADEL with Pylon
sidebar_label: Pylon
---
import AppJWT from "../imports/_app_jwt.mdx";
import ServiceuserJWT from "../imports/_serviceuser_jwt.mdx";
import ServiceuserRole from "../imports/_serviceuser_role.mdx";
import SetupPylon from "../imports/_setup_pylon.mdx";
This integration guide demonstrates the recommended way to incorporate ZITADEL into your Pylon service.
It explains how to check the token validity in the API and how to check for permissions.
By the end of this guide, your application will have three different endpoint which are public, private(valid token) and private-scoped(valid token with specific role).
## ZITADEL setup
Before we can start building our application, we have to do a few configuration steps in ZITADEL Console.
### Create application
<AppJWT />
### Create Serviceuser
<ServiceuserJWT />
### Give Serviceuser an authorization
<ServiceuserRole />
### Prerequisites
At the end you should have the following for the API:
- Issuer, something like `https://example.zitadel.cloud` or `http://localhost:8080`
- `.json`-key-file for the API, from the application
- ID of the project
And the following from the Serviceuser:
- `.json`-key-file from the serviceuser
## Setup new Pylon service
### Setup Pylon
<SetupPylon />
### Creating a new project
To create a new Pylon project, run the following command:
```bash
pylon new my-pylon-project
```
This will create a new directory called `my-pylon-project` with a basic Pylon project structure.
### Project structure
Pylon projects are structured as follows:
```
my-pylon-project/
├── .pylon/
├── src/
│ ├── index.ts
├── package.json
├── tsconfig.json
```
- `.pylon/`: Contains the production build of your project.
- `src/`: Contains the source code of your project.
- `src/index.ts`: The entry point of your Pylon service.
- `package.json`: The npm package configuration file.
- `tsconfig.json`: The TypeScript configuration file.
### Basic example
Here's an example of a basic Pylon service:
```ts
import { defineService } from "@getcronit/pylon";
export default defineService({
Query: {
sum: (a: number, b: number) => a + b,
},
Mutation: {
divide: (a: number, b: number) => a / b,
},
});
```
## Secure the API
### Add ZITADEL info to the service
1. Create a `.env` file in the root folder of your project and add the following configuration:
```bash
AUTH_ISSUER='URL to the zitadel instance'
AUTH_PROJECT_ID='ID of the project'
```
It should look something like this:
```bash
AUTH_ISSUER='https://example.zitadel.cloud'
AUTH_PROJECT_ID='250719519163548112'
```
2. Copy the `.json`-key-file that you downloaded from the ZITADEL Console into the root folder of your project and rename it to `key.json`.
### Auth
Pylon provides a auth module and a decorator to check the validity of the token and the permissions.
- `auth.initialize()`: Initializes the authentication middleware.
- `auth.require()` : Middleware to check if the token is valid.
- `auth.require({roles: ['role']})`: Middleware to check if the token is valid and has the specified roles.
- `requireAuth()`: Decorator to check if the token is valid.
- `requireAuth({roles: ['role']})`: Decorator to check if the token is valid and has the specified roles.
### Build the Pylon service
Now we will create a new Pylon service with the following endpoints:
- `/api/public`: Public endpoint
- `/api/private`: Private endpoint
- `/api/private-scoped`: Private endpoint with specific role
- `/graphql`: GraphQL endpoint
- Query: `me`: Private endpoint that returns the current user and the messages if the role is `read:messages`
- Query: `info`: Public endpoint
### Create the service
The following code demonstrates how to create a Pylon service with the required endpoints, it must be added to the `src/index.ts` file of your project:
```ts
import {
defineService,
PylonAPI,
auth,
requireAuth,
getContext,
ServiceError,
} from "@getcronit/pylon";
class User {
id: string;
name: string;
#messages: string[];
constructor(id: string, name: string, messages: string[]) {
this.id = id;
this.name = name;
this.#messages = messages;
}
@requireAuth({ roles: ["read:messages"] })
async messages() {
return this.#messages;
}
static users: User[] = [];
@requireAuth()
static async me() {
const ctx = getContext();
const id = ctx.get("auth")!.sub;
const user = User.users.find((user) => user.id === id);
if (!user) {
throw new ServiceError("User not found", {
statusCode: 404,
code: "USER_NOT_FOUND",
});
}
return user;
}
@requireAuth()
static async create() {
const ctx = getContext();
const auth = ctx.get("auth")!;
// Check if the user already exists
if (User.users.find((user) => user.id === auth.sub)) {
throw new ServiceError("User already exists", {
statusCode: 400,
code: "USER_ALREADY_EXISTS",
});
}
const user = new User(auth.sub, auth.username || "unknown", [
"Welcome to Pylon with ZITADEL!",
]);
User.users.push(user);
return user;
}
}
export default defineService({
Query: {
me: User.me,
info: () => "Public Data",
},
Mutation: {
createUser: User.create,
},
});
export const configureApp: PylonAPI["configureApp"] = (app) => {
// Initialize the authentication middleware
app.use("*", auth.initialize());
// Automatically try to create a user for each request for demonstration purposes
app.use(async (_, next) => {
try {
await User.create();
} catch {
// Ignore errors
// Fail silently if the user already exists
}
await next();
});
app.get("/api/info", (c) => {
return new Response("Public Data");
});
// The `auth.require()` middleware is optional here, as the `User.me` method already checks for it.
app.get("/api/me", auth.require(), async (c) => {
const user = await User.me();
return c.json(user);
});
// A role check for `read:messages` is not required here, as the `user.messages` method already checks for it.
app.get("/api/me/messages", auth.require(), async (c) => {
const user = await User.me();
// This will throw an error if the user does not have the `read:messages` role
return c.json(await user.messages());
});
};
```
### Call the API
To call the API you need an access token, which is then verified by ZITADEL.
Please follow [this guide here](/docs/guides/integrate/token-introspection/private-key-jwt#get-an-access-token), ignoring the first step as we already have the `.json`-key-file from the serviceaccount.
:::info
You can also create a PAT for the serviceuser and use it to test the API. For this, follow [this guide](/docs/guides/integrate/service-users/personal-access-token#create-a-service-user-with-a-pat).
:::
Optionally set the token as an environment variable:
```
export TOKEN='MtjHodGy4zxKylDOhg6kW90WeEQs2q...'
```
Now you have to start the Pylon service:
```bash
bun run develop
```
With the access token, you can then do the following calls:
1. GraphQL:
```
curl -H "Authorization: Bearer $TOKEN" -G http://localhost:3000/graphql --data-urlencode 'query={ info }'
curl -H "Authorization: Bearer $TOKEN" -G http://localhost:3000/graphql --data-urlencode 'query={ me { id name } }'
curl -H "Authorization: Bearer $TOKEN" -G http://localhost:3000/graphql --data-urlencode 'query={ me { id name messages } }'
```
You can also visit the GraphQL playground at `http://localhost:3000/graphql` and execute the queries there.
2. Routes:
```
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:3000/api/info
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:3000/api/me
curl -H "Authorization: Bearer $TOKEN" -X GET http://localhost:3000/api/me/messages
```
## Completion
Congratulations! You have successfully integrated your Pylon with ZITADEL!
If you get stuck, consider checking out their [documentation](https://pylon.cronit.io/). If you face issues, contact Pylon or raise an issue on [GitHub](https://github.com/getcronit/pylon/issues).

View File

@ -111,5 +111,11 @@
"imgSrcDark": "/docs/img/tech/rustlight.svg",
"docsLink": "https://github.com/smartive/zitadel-rust",
"external": true
},
{
"title": "Pylon",
"imgSrcDark": "/docs/img/tech/pylon.svg",
"docsLink": "https://github.com/getcronit/pylon",
"external": true
}
]

View File

@ -38,6 +38,7 @@ module.exports = {
"examples/secure-api/python-django",
"examples/secure-api/python-flask",
"examples/secure-api/nodejs-nestjs",
"examples/secure-api/pylon",
{
type: "link",
label: ".Net",
@ -100,6 +101,11 @@ module.exports = {
label: "Rust",
href: "https://github.com/smartive/zitadel-rust",
},
{
type: "link",
label: "Pylon",
href: "https://github.com/getcronit/pylon",
},
],
},
{
@ -163,7 +169,7 @@ module.exports = {
"guides/manage/customize/user-schema",
],
},
"guides/manage/terraform-provider"
"guides/manage/terraform-provider",
],
},
{
@ -280,7 +286,7 @@ module.exports = {
label: "Service Users",
link: {
type: "doc",
id: "guides/integrate/service-users/authenticate-service-users"
id: "guides/integrate/service-users/authenticate-service-users",
},
collapsed: true,
items: [
@ -323,7 +329,10 @@ module.exports = {
{
type: "category",
label: "Login users with SSO",
link: { type: "doc", id: "guides/integrate/identity-providers/introduction" },
link: {
type: "doc",
id: "guides/integrate/identity-providers/introduction",
},
collapsed: true,
items: [
"guides/integrate/identity-providers/google",
@ -349,7 +358,7 @@ module.exports = {
label: "ZITADEL APIs",
link: {
type: "doc",
id: "guides/integrate/zitadel-apis/access-zitadel-apis"
id: "guides/integrate/zitadel-apis/access-zitadel-apis",
},
collapsed: true,
items: [
@ -478,9 +487,8 @@ module.exports = {
{
type: "autogenerated",
dirName: "concepts/structure",
}
]
},
],
},
{
type: "category",
@ -490,9 +498,8 @@ module.exports = {
{
type: "autogenerated",
dirName: "concepts/features",
}
]
},
],
},
{
type: "autogenerated",
@ -807,10 +814,7 @@ module.exports = {
type: "category",
label: "Actions V2",
collapsed: false,
items: [
"apis/actionsv2/introduction",
"apis/actionsv2/execution-local",
],
items: ["apis/actionsv2/introduction", "apis/actionsv2/execution-local"],
},
{
type: "doc",
@ -883,11 +887,9 @@ module.exports = {
collapsed: false,
link: {
type: "doc",
id: "self-hosting/manage/cli/overview"
id: "self-hosting/manage/cli/overview",
},
items: [
"self-hosting/manage/cli/mirror"
],
items: ["self-hosting/manage/cli/mirror"],
},
],
},

1
docs/static/img/tech/pylon.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" zoomAndPan="magnify" viewBox="0 0 375 374.999991" height="500" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><clipPath id="0b6ffeb5c8"><path d="M 187.5 0 C 83.945312 0 0 83.945312 0 187.5 C 0 291.054688 83.945312 375 187.5 375 C 291.054688 375 375 291.054688 375 187.5 C 375 83.945312 291.054688 0 187.5 0 Z M 187.5 0 " clip-rule="nonzero"/></clipPath><clipPath id="3768169a3c"><path d="M 137 94.300781 L 238 94.300781 L 238 120 L 137 120 Z M 137 94.300781 " clip-rule="nonzero"/></clipPath><clipPath id="e990912f0b"><path d="M 86.097656 275 L 289 275 L 289 297.503906 L 86.097656 297.503906 Z M 86.097656 275 " clip-rule="nonzero"/></clipPath></defs><g clip-path="url(#0b6ffeb5c8)"><rect x="-37.5" width="450" fill="#000000" y="-37.499999" height="449.999989" fill-opacity="1"/></g><path fill="#ffffff" d="M 267.359375 242.78125 L 107.640625 242.78125 L 101.558594 268.128906 L 273.4375 268.128906 Z M 267.359375 242.78125 " fill-opacity="1" fill-rule="nonzero"/><g clip-path="url(#3768169a3c)"><path fill="#ffffff" d="M 223.167969 94.300781 L 151.828125 94.300781 C 151.21875 94.304688 150.617188 94.355469 150.019531 94.457031 C 149.421875 94.558594 148.835938 94.707031 148.261719 94.910156 C 147.6875 95.109375 147.136719 95.355469 146.605469 95.644531 C 146.074219 95.9375 145.570312 96.273438 145.09375 96.648438 C 144.617188 97.023438 144.175781 97.4375 143.769531 97.886719 C 143.363281 98.339844 142.996094 98.820312 142.667969 99.332031 C 142.34375 99.84375 142.058594 100.378906 141.824219 100.9375 C 141.585938 101.496094 141.398438 102.070312 141.257812 102.660156 L 137.179688 119.652344 L 237.835938 119.652344 L 233.734375 102.632812 C 233.59375 102.042969 233.402344 101.46875 233.167969 100.914062 C 232.929688 100.355469 232.644531 99.820312 232.320312 99.3125 C 231.992188 98.800781 231.625 98.320312 231.21875 97.875 C 230.8125 97.425781 230.371094 97.011719 229.894531 96.636719 C 229.417969 96.261719 228.914062 95.929688 228.382812 95.640625 C 227.851562 95.347656 227.300781 95.101562 226.726562 94.90625 C 226.15625 94.707031 225.570312 94.554688 224.972656 94.453125 C 224.375 94.351562 223.773438 94.300781 223.167969 94.300781 Z M 223.167969 94.300781 " fill-opacity="1" fill-rule="nonzero"/></g><g clip-path="url(#e990912f0b)"><path fill="#ffffff" d="M 89.71875 275.371094 L 285.277344 275.371094 C 285.757812 275.371094 286.21875 275.464844 286.660156 275.648438 C 287.105469 275.832031 287.496094 276.09375 287.835938 276.433594 C 288.175781 276.773438 288.4375 277.164062 288.621094 277.609375 C 288.804688 278.050781 288.898438 278.511719 288.898438 278.992188 L 288.898438 293.480469 C 288.898438 293.960938 288.804688 294.421875 288.621094 294.867188 C 288.4375 295.308594 288.175781 295.699219 287.835938 296.039062 C 287.496094 296.378906 287.105469 296.640625 286.660156 296.824219 C 286.21875 297.007812 285.757812 297.101562 285.277344 297.101562 L 89.71875 297.101562 C 89.238281 297.101562 88.777344 297.007812 88.335938 296.824219 C 87.890625 296.640625 87.5 296.378906 87.160156 296.039062 C 86.820312 295.699219 86.558594 295.308594 86.375 294.867188 C 86.191406 294.421875 86.097656 293.960938 86.097656 293.480469 L 86.097656 278.992188 C 86.097656 278.511719 86.191406 278.050781 86.375 277.609375 C 86.558594 277.164062 86.820312 276.773438 87.160156 276.433594 C 87.5 276.09375 87.890625 275.832031 88.335938 275.648438 C 88.777344 275.464844 89.238281 275.371094 89.71875 275.371094 Z M 89.71875 275.371094 " fill-opacity="1" fill-rule="nonzero"/></g><path fill="#ffffff" d="M 128.5 155.867188 L 246.5 155.867188 L 239.546875 126.894531 L 135.453125 126.894531 Z M 128.5 155.867188 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 109.378906 235.535156 L 265.617188 235.535156 L 256.925781 199.324219 L 118.070312 199.324219 Z M 109.378906 235.535156 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 126.753906 163.109375 L 119.804688 192.078125 L 255.191406 192.078125 L 248.242188 163.109375 Z M 126.753906 163.109375 " fill-opacity="1" fill-rule="nonzero"/></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB