Skip to main content

Integrate Login into Your App

This guide takes a subscriber's application from "nothing" to "users log in through Curupira". It assumes you already have a tenant, an application (client_id + API key), and at least one redirect URI registered. If not, see Getting Started.

The mechanics of the flow — every parameter, request, and response — are in Authentication. This page is the pragmatic checklist and shows two common integration styles.

Prerequisites checklist

  • A tenant provisioned for your organization.
  • An application with its client_id (UUID) and API key (secret).
  • Your callback URL registered as a redirect URI on the application (exact match, including scheme, host, port, and path).
  • Decided your client type:
    • Public (SPA, mobile, desktop) → PKCE only, no API key in the client.
    • Confidential (server-rendered web app, backend) → PKCE and the API key, kept server-side.

Because Curupira ships a standard discovery document, you rarely need to hand-code the flow. Point any OIDC client library at the issuer and it configures itself:

issuer: https://auth.curupira.api.br
discovery URL: https://auth.curupira.api.br/.well-known/openid-configuration
client_id: <YOUR_CLIENT_ID>
redirect_uri: https://app.example.com/callback
scope: openid email profile
response_type: code
PKCE: S256 (required)

Well-tested options: oidc-client-ts / oauth4webapi (browser & Node), openid-client (Node), AppAuth (iOS/Android), Authlib (Python), coreos/go-oidc (Go). Enable PKCE (most libraries do by default) and, for confidential clients, configure the token-endpoint call to send the X-API-Key header.

Confidential (server-side web app) — the flow

  1. Login route (GET /login): generate state + PKCE code_verifier/code_challenge, store both in the user's session, and redirect to https://auth.curupira.api.br/oauth2/authorize?... (see Authentication §3).
  2. Callback route (GET /callback): validate state, then POST to https://auth.curupira.api.br/oauth2/token with grant_type=authorization_code, the code, the stored code_verifier, your client_id, and the X-API-Key header.
  3. Verify the id_token signature against the JWKS; check iss, aud, exp.
  4. Establish your app's own session from the verified claims (store sub, email, roles, permissions, and the refresh_token server-side).
  5. Refresh with grant_type=refresh_token when the access token nears expiry — and persist the rotated refresh token returned each time.
# Step 2, concretely (server-side, API key stays on the server):
curl -s -X POST https://auth.curupira.api.br/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "X-API-Key: $CURUPIRA_API_KEY" \
-d "grant_type=authorization_code" \
-d "code=$AUTH_CODE" \
-d "redirect_uri=https://app.example.com/callback" \
-d "client_id=$CURUPIRA_CLIENT_ID" \
-d "code_verifier=$CODE_VERIFIER"

Public (SPA / mobile) — the flow

Identical, except the client never holds the API key. Security comes from PKCE plus the exact-match redirect URI. Use a library that keeps tokens in memory (SPAs) or the platform secure storage (mobile), and send the token request without X-API-Key.

Using roles & permissions in your app

After you verify the id_token, read:

  • roles — the role names the user holds in this application (e.g. ["editor"]).
  • permissions — the aggregated permission strings from those roles.
  • tenant — the tenant slug the user belongs to.

Gate features on these claims. Create the roles/role-groups your app expects in the admin dashboard and assign them to users — see Product Concepts.

Common pitfalls

  • redirect_uri mismatch → the authorize request is rejected. It must match a registered URI character-for-character.
  • Dropping the rotated refresh token → the next refresh fails with invalid_grant. Always save the newest one.
  • Shipping the API key to the browser → treat public clients as PKCE-only; keep the key server-side for confidential clients.
  • Trusting an unverified JWT → always validate the signature against the JWKS and check iss / aud / exp.

Next steps