ZitAuth is a centralized authentication gateway that transforms Zitadel integration from a headache into a breeze. One clean API for all your appsβweb, mobile, and backend services.
Why ZitAuth? Because your applications shouldn't care about JWT validation, JWKS endpoints, or OIDC flows. They should just work.
π Bulletproof User Authentication: OIDC login with PKCE flow
π€ Effortless Service-to-Service Auth: M2M authentication with JWT Bearer Grant
β
One-Stop Token Validation: Single /validate endpoint handles all tokens
π― Ready-to-Run Examples: Complete user and machine authentication simulation
π Dual Implementation: Available in both Python (FastAPI) and Node.js (Express)
graph TD
subgraph "Clients"
User[π€ User]
M2MClient[π» Backend Service]
end
subgraph "Application Layer"
SPA[π SPA / Web App]
ProtectedAPI[π¦ Your Protected API]
end
subgraph "Authentication Core"
ZitAuth[π‘οΈ ZitAuth Gateway]
Zitadel[π Zitadel IdP]
end
%% User Authentication Flow
User -- "Initiates Login" --> SPA
SPA -- "Redirects for Auth" --> ZitAuth
ZitAuth -- "Handles OIDC Flow" --> Zitadel
Zitadel -- "Authenticates & Issues Code" --> ZitAuth
ZitAuth -- "Exchanges Code for Token" --> Zitadel
ZitAuth -- "Returns Token" --> SPA
SPA -- "Calls API with User Token" --> ProtectedAPI
%% M2M Authentication Flow
M2MClient -- "Requests M2M Token" --> ZitAuth
ZitAuth -- "Handles JWT Bearer Grant" --> Zitadel
Zitadel -- "Issues M2M Token" --> ZitAuth
ZitAuth -- "Returns Token" --> M2MClient
M2MClient -- "Calls API with M2M Token" --> ProtectedAPI
%% Centralized Validation
ProtectedAPI -- "Validates Token via Gateway" --> ZitAuth
- Docker and Docker Compose
- Python 3.11+
pipfor installing dependencies
The provided
docker-compose.yamlfile is used to set up a local Zitadel instance. You can also use Zitadel hosted on the cloud.docker compose up -d
You can access the Zitadel Console at
http://localhost:8080.
After starting Zitadel, log in to the console at http://localhost:8080 using these creds:
- username:
zitadel-admin@zitadel.localhost - password:
Password1!
- username:
You will need to create a project and application within it (for mobile login flow) and a service user (for m2m flow) and configure login policies (Required for User registration)
Click to expand Zitadel Application Configuration
- In your project, create a new application of type
User Agent - Select authentication method as
PKCE, toggle theDevelopment modeto allow redirect to http - Configure the redirect URI as
http://localhost:8000/api/v1/callback - After creating the application, Go to the
Token Settingstab and selectAuth Token TypeasJWT - Go to the URLs tab and note down the urls and client id in .env file
Click to expand Zitadel Service User Configuration
- In the Zitadel console, go to
UsersSection and then to theService Userstab - Click on the
Newbutton - Fill basic details and select the
Access Token TypeasJWT - Go to
Keystab and add a new key of typeJSONand download it - Save this file to a secure location in your project. Provide the path to this file in the
.envfile
Click to expand Zitadel Login Policy Configuration
- Navigate to the `Default Settings` setion on the zitadel console - Go to the `External Links` tab in the `Other` subsection - Configure `Link to Terms of Service` and `Link to Privacy Policy` (or use any dummy links for demo) - Click `Save`- In your project, create a new application of type
Create the
.envfile by copying the example:cp .env.example .env
Edit the
.envfile:- Fill the environment variables with the information received from the step before
Install dependencies:
pip install -r requirements.txt
The start.sh script is provided to run the main components. You can inspect the script to see the individual commands.
Start the main services:
# This will start ZitAuth on port 8000
uvicorn python.main:app --reload --port 8000Note - For running in nodejs, refer to nodejs/README.md
Start the SPA app:
uvicorn examples.spa_app.main:app --reload --port 3001
Open your browser and navigate to the SPA at
http://127.0.0.1:3001Click the "Login via ZitAuth" button. You will be redirected to the Zitadel login page
Log in with a user or register one
After a successful login, you will be redirected back to the SPA, and the status will show "Access token received"
Click
Call Protected EndpointandFetch User Infoto test the authenticated API calls
Ensure the main services (ZitAuth Gateway and SPA App) are running
In a new terminal, run the M2M simulation script:
python examples/m2m_sim.py
Observe the logs. The script will request an M2M token, receive it, and use it to successfully call the protected API
.
βββ docker-compose.yaml # Sets up Zitadel server locally
βββ examples/
β βββ m2m_sim.py # Simulates the local service (M2M)
β βββ spa_app/ # Simulates the mobile/web application
βββ python/
β βββ client.py # The core ZitadelClient abstraction layer
β βββ main.py # The FastAPI service (ZitAuth Gateway)
β βββ utils.py # Helper functions
βββ .env.example # Template for environment variables
βββ requirements.txt # Python dependencies
βββ start.sh # Helper script to run services
Click to expand API Endpoint Documentation
Initiates the OIDC user login flow. This endpoint is intended to be used by a browser, which will be redirected.
- Description: Starts the user authentication process by redirecting the user to the Zitadel login page
- Request: No parameters or headers required
- Response (Success):
HTTP 302 Found: A redirect to the Zitadel authorization endpoint
- Response (Error):
HTTP 400 Bad Request: If there is an internal error generating the login URL
Handles the OIDC callback from Zitadel after a user authenticates. This endpoint is used by the browser as part of the redirect flow.
- Description: Zitadel redirects the user's browser to this endpoint after a successful login. The endpoint exchanges the received authorization
codefor an access token - Request:
- Query Parameters:
Parameter Description codeThe authorization code issued by Zitadel stateThe unique state string used to prevent CSRF attacks
- Query Parameters:
- Response (Success):
HTTP 302 Found: Redirects the user's browser back to the SPA (SPA_ORIGIN), with theaccess_tokenincluded in the URL hash fragment
- Response (Error):
HTTP 400 Bad Request: If thestateis invalid, expired, or the token exchange fails
Issues a machine-to-machine (M2M) access token using a pre-configured service account.
- Description: Allows a trusted backend service to acquire an access token by handling the JWT Bearer Grant flow on behalf of the service
- Request: No parameters or headers required. The service authenticates itself by its ability to call this endpoint
- Response (Success):
HTTP 200 OK- Body (JSON):
{ "access_token": "ey..." }
- Response (Error):
HTTP 500 Internal Server Error: If the service account file is misconfigured or Zitadel rejects the request
Validates an access token and returns the authentication status.
- Description: A centralized endpoint for any service to delegate token validation. It checks the token's signature against Zitadel's public keys using its JWKS endpoint
- Request:
- Headers:
Header Description AuthorizationRequired. The bearer token. Must be in the format Bearer <token>
- Headers:
- Response (Success):
HTTP 200 OK- Body (JSON): The decoded claims (payload) of the JWT
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "exp": 1516242622, "iss": "http://localhost:8080" }
- Response (Error):
HTTP 400 Bad Request: If theAuthorizationheader is missing or malformedHTTP 401 Unauthorized: If the token is invalid (expired, bad signature, etc.)
Fetches the user profile from Zitadel's userinfo endpoint using a valid access token.
- Description: Acts as a secure proxy to Zitadel's userinfo endpoint
- Request:
- Headers:
Header Description AuthorizationRequired. The bearer token. Must be in the format Bearer <token>
- Headers:
- Response (Success):
HTTP 200 OK- Body (JSON): The user profile information
{ "userinfo": { "sub": "1234567890", "name": "John Doe", "email": "john.doe@example.com", "email_verified": true } }
- Response (Error):
HTTP 400 Bad Request: If theAuthorizationheader is missingHTTP 401 Unauthorized: If the access token is invalid or does not have the required scopes
