Authentication overview

Huudis is the identity provider — it's the thing other Forjio products delegate sign-in to. Where the Plugipay docs describe being a relying party, this page describes what's actually happening on the other side of the redirect.

If you're integrating an app with Huudis, you're a relying party (a "client" in OIDC terms). Huudis speaks standard OpenID Connect plus a few extensions documented below.

OIDC, not a bespoke protocol. Huudis implements OIDC 1.0 with PKCE (RFC 7636), device flow (RFC 8628), and refresh-token rotation. Any compliant OIDC library — Auth.js, oidc-client-js, authlib, go-oidc — works against Huudis without modification.

Discovery

Every Huudis instance publishes an OIDC discovery document and a JWKS:

GET https://huudis.com/.well-known/openid-configuration
GET https://huudis.com/.well-known/jwks.json

Point your OIDC library at the discovery URL and it will find every endpoint it needs.

The three flows

Huudis supports three OAuth 2.0 grant types, one for each audience:

Audience Grant type When to use
A user in a web/mobile app authorization_code + PKCE Default. The user's browser does the redirect dance.
A CLI on the user's terminal urn:ietf:params:oauth:grant-type:device_code No browser-on-localhost trickery; the CLI prints a code and a URL.
A long-lived session refresh_token Mint a fresh access token without re-prompting the user.

There is no client_credentials grant (yet). For service-to-service auth, use a long-lived access token minted from a service-account user — see API authentication.

Authorization code flow (the short version)

The five steps:

  1. Your app redirects the user to huudis.com/api/v1/oidc/authorize with a client_id, redirect_uri, scope, state, and PKCE code_challenge.
  2. Huudis prompts for credentials — email/password, Google, Apple, or Facebook depending on workspace config.
  3. After successful sign-in, Huudis redirects the browser to your redirect_uri?code=…&state=….
  4. Your backend POSTs the code (plus the original PKCE code_verifier) to /api/v1/oidc/token.
  5. Huudis returns an access_token, id_token, and refresh_token. You're signed in.

Authorization code flow (the longer version)

Step 1 — Authorize

GET /api/v1/oidc/authorize
  ?response_type=code
  &client_id=oc_xxx
  &redirect_uri=https://myapp.com/callback
  &scope=openid%20profile%20email
  &state=<csrf-token>
  &code_challenge=<sha256(verifier) base64url>
  &code_challenge_method=S256

Standard parameters. PKCE is required — Huudis rejects requests without code_challenge, even for confidential clients, because the cost is near zero and it neutralises a whole class of auth-code-interception attacks.

Step 2 — User authenticates

Huudis renders its own sign-in screen. The user picks their method:

  • Email + password — covered in Sign in.
  • Google / Apple / Facebook — covered in Social providers.
  • Existing session — if Huudis already has a cookie for this user (because they signed into another Forjio product), step 2 is skipped silently. This is single sign-on.

If the user has MFA enrolled, Huudis presents a second factor challenge before completing.

Step 3 — Redirect with code

HTTP/1.1 302 Found
Location: https://myapp.com/callback?code=auc_xxxxxxxxxxxxxxxxxxxxxxxxx&state=<csrf-token>

The code is short (30 chars), single-use, and expires in 60 seconds.

Step 4 — Token exchange

POST /api/v1/oidc/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=auc_xxx&
redirect_uri=https://myapp.com/callback&
client_id=oc_xxx&
client_secret=ocs_xxx&
code_verifier=<the verifier you saved>

For public clients (mobile, SPA), omit client_secret — PKCE alone is sufficient.

Step 5 — Tokens

{
  "access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6...",
  "id_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6...",
  "refresh_token": "rft_xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "token_type": "Bearer",
  "expires_in": 21600,
  "scope": "openid profile email"
}

Both access_token and id_token are JWTs signed with ES256, verifiable against the published JWKS. Access tokens live six hours; refresh tokens live 30 days but rotate on every use.

Refresh-token rotation

Every time you call /oidc/token with grant_type=refresh_token, Huudis:

  1. Validates the presented refresh token.
  2. Issues a new access token and a new refresh token.
  3. Revokes the presented refresh token.

If Huudis ever sees the same refresh token presented twice (because it was leaked, or because two browser tabs raced each other), Huudis treats it as a stolen-token signal and revokes the entire token family — every refresh token derived from the original. The user is forced back through sign-in.

Single-flight your refreshes. Two simultaneous refresh calls with the same token will both succeed at the first call but the second will get rejected and kill the family. Always serialise refresh through one in-process mutex. The Huudis SDKs do this for you.

Device flow

For CLIs and TVs where there's no browser-on-localhost:

POST /api/v1/oidc/device_authorization
Content-Type: application/x-www-form-urlencoded

client_id=oc_xxx&scope=openid%20profile

Response:

{
  "device_code": "dev_xxxxxxxxxxxxxxxxxxxxxxxx",
  "user_code": "A3F7-9C2D",
  "verification_uri": "https://huudis.com/device",
  "verification_uri_complete": "https://huudis.com/device?user_code=A3F7-9C2D",
  "expires_in": 600,
  "interval": 5
}

Show the user the URL + code. They open it on any device already signed into Huudis, approve, and your CLI polls /oidc/token with grant_type=urn:ietf:params:oauth:grant-type:device_code until it sees an access token.

The huudis CLI uses this flow end-to-end.

What can go wrong

  • invalid_client — The client_id doesn't match a registered client, or the redirect URI isn't in the client's allow-list. Check the OIDC clients page.
  • invalid_grant — The authorization code is expired (60s window), already used, or PKCE verifier didn't match. Codes are one-shot — never retry the exchange.
  • mfa_required — The user has MFA enrolled and the workspace policy requires it for this client. Your app needs to honour the MFA prompt; the SDKs handle this automatically.
  • token family revoked — Refresh-token reuse was detected. Force a fresh sign-in via the authorize endpoint.

Next