Skip to main content
This article applies to implementing authorization and authentication in your own apps (e.g. vibe coded apps) using Peliqan as the backend and using Peliqan OAuth2 for authorization and authentication of your app users.

Before you start

  • Contact Peliqan support and request activation of OAuth Apps in your Peliqan account
  • Register an OAuth2 app in your Peliqan account: Settings → OAuth2 Apps, and select at least one scope
  • Public app (no client secret) — browser/mobile apps. Use authorization_code; it also gets a rotating refresh_token (no secret needed, PKCE plus rotation cover that).
  • Confidential app (has a client secret). Can additionally use client_credentials (server-to-server, no user), and must also send client_secret when refreshing a token.
  • PKCE (code_challenge / code_verifier, S256 only) is mandatory for authorization_code, there is no way to opt out (RFC 9700).
  • Always pass an explicit scope. An authorize request with no scope and no default_scopes configured on the app is rejected with invalid_scope.

Base URLs

All examples below use a BASE_URL constant — set it to whichever of these applies to you.

Endpoint reference

1. Authorization code flow (user login)

This is “Login with Peliqan” — the user’s browser visits Peliqan, logs in, approves your app, and gets redirected back to you with a code. PKCE requires a code_verifier you generate and keep secret, and a code_challenge (its SHA-256 hash) you send up front.
Works in Node.js 18+, browsers, Bun, and Deno — uses only fetch and the global Web Crypto API (crypto.subtle), no packages.

2. Refreshing a token

Peliqan rotates refresh tokens on every use — the old one is revoked, so always persist the new refresh_token from the response, not just the new access token. Both public and confidential apps get a refresh token; confidential apps must additionally authenticate with client_secret on every refresh — public apps don’t send one.

3. Client credentials (server-to-server, no user)

Requires a confidential app (has a client secret) — this grant is not available to public apps. Fully scriptable, no browser needed.

4. Other endpoints

Revoke, introspect, get the logged-in user’s identity, list available scopes, and fetch the RFC 8414 discovery document.

Error handling

Every non-2xx response returns a machine-readable OAuth2 error code — branch on that field, not the message string.
Wrap the raw fetch call once and throw a typed error carrying the error code, so every caller can branch on err.code instead of string-matching:

Gotchas

  • PKCE is not optional. Every authorization_code request needs code_challenge (S256) or the authorize call is rejected — there’s no legacy non-PKCE path.
  • No scope is added automatically. You get exactly what’s registered on the app plus whatever you explicitly request — nothing (including profile:read) is silently injected.
  • Refresh tokens rotate, for every client type. The old one is revoked the instant you use it — always persist the new refresh_token from the response, or the next refresh will fail with invalid_grant. Public apps get refresh tokens too (PKCE + rotation stand in for a client secret); only confidential apps additionally need to send client_secret on refresh.
  • Token endpoint is rate-limited to 20 requests/minute per IP.
  • state verification is on you. The server doesn’t store or check it — you must compare the state you generated against the one that comes back on the redirect, or you’re open to CSRF.
  • On-prem/white-label: just point BASE_URL at your own domain — the flow is otherwise identical.