Skip to content
  • There are no suggestions because the search field is empty.

Implementing Human-in-the-Loop (HITL) Authentication with Zitadel and Web Push

How to safely grant elevated permissions to background bots using on-demand human verification

Architectural Overview

Traditional HITL flows often require users to manually copy and paste verification codes between screens. By combining Progressive Web App (PWA) push notifications with Zitadel's API, we can eliminate the manual steps entirely.

Here is the lifecycle of a secure HITL request:

  1. Device Registration: A human user logs into the main application and enables PWA notifications. The browser generates a secure Web Push subscription object, which is then saved to the user's Zitadel Profile Metadata.

  2. Access Request: A background bot needs to perform an action on behalf of that user, for example, to make a purchase online. It initiates the OAuth2 Device Code flow via Zitadel.

  3. The Notification: The bot reads the user's Push Subscription from Zitadel and sends a native OS notification containing the authorization link.

  4. Human Approval: The user clicks the native OS notification on their device, which opens Zitadel's login screen with the device code pre-filled. If the user has a valid SSO session, Zitadel will only prompt the user to approve the request.

  5. Token Acquisition: The bot, which has been polling Zitadel in the background, detects the successful login, receives the secure JWT, and resumes its task.


Prerequisites in Zitadel

To implement this architecture, you will need to configure three specific entities in your Zitadel project:

  • A Web Application (OIDC): For the human user to log into your app (e.g., using NextAuth.js).

  • A Native Application: For the bot to initiate the Device Code flow. Native apps do not require client secrets, making them perfect for public or distributed bots.

     

     

  • A Service User & PAT: A machine user with a Personal Access Token (PAT) that has permissions to read and write User Metadata via the Zitadel Management API.


Phase 1: Binding the Device to the Identity

For the bot to know where to send the notification, the human operator must first bind their physical browser/device to their Zitadel identity.

  • The user logs into your web portal, for example, the online store that allow a bot to make purchases automatically on the background.

  • The browser requests permission to show notifications using the Notification and ServiceWorker APIs.

  • The frontend generates a secure subscription object using VAPID keys.

  • The backend uses the Zitadel v2.UserService/SetUserMetadata endpoint to securely store this JSON subscription object against the user's userId under a key like push_subscription.

Phase 2: The Bot Initiates the Flow

When the automated process reaches a point where it requires human approval:

  1. Read Metadata: The bot fetches the target user's push_subscription from Zitadel using the Service User PAT.

  2. Request Device Code: The bot sends a POST request to Zitadel's /oauth/v2/device_authorization endpoint using the Native App Client ID. Zitadel returns a device_code, a user_code, and a verification_uri_complete.

  3. Fire Web Push: The bot utilizes a library (like web-push in Node.js) to push the verification_uri_complete payload directly to the user's registered browser endpoint.

  4. Start Polling: The bot begins polling Zitadel's /oauth/v2/token endpoint at the required interval, waiting for the user to approve the request.

Phase 3: Frictionless User Experience

Because the bot sent the verification_uri_complete (which includes the code embedded in the URL), the user experience is entirely frictionless.

  • The user receives a native OS popup (Mac, Windows, iOS, or Android).

  • Clicking the notification triggers the Service Worker to open a new browser tab.

  • The tab points directly to Zitadel. Because the user is likely already authenticated in their browser, they only need to click "Approve" (or authenticate via Passkey/Biometrics if session expired).

Phase 4: Security Validation (Crucial Step)

Once the user approves the prompt, Zitadel returns an access_token and an id_token to the polling bot.

Do not blindly trust the token. Because the bot sent the push notification to a specific user, the bot must verify that the human who clicked "Approve" is the same human the bot intended to ask.

Before executing the sensitive action, the bot must:

  1. Decode the id_token.

  2. Extract the sub (Subject ID) claim.

  3. Verify that sub === ExpectedUserId.

If a different user intercepted the link and approved it, the system should immediately reject the token and abort the operation.