aswad
Developer documentation

Paswad Developer Docs

Passwordless authentication, OAuth 2.1 + OIDC, and signed webhooks — integrated in minutes. Paswad is a standard OpenID Connect provider: your users authenticate with a passkey, you get verified identity. No passwords to store, nothing to breach.

Introduction

Paswad lets people log into your application with a passkey instead of a password. “Sign in with Paswad” is, under the hood, a textbook OAuth 2.1 Authorization Code flow with PKCE, layered with OpenID Connect for identity. If you have integrated “Sign in with Google” before, this will feel familiar.

Passwordless

Users authenticate with a device-bound passkey. Phishing-resistant by design.

Standards-based

OAuth 2.1, OIDC discovery, and JWKS. Use any compliant client library you already trust.

Verified identity

Optional verified profile claims — name and email confirmed at the source.

Base hosts. Authorization happens on id.paswad.com. Tokens, user info, and discovery live on api.paswad.com. You manage apps in console.paswad.com. See Environments & base URLs.

Environments & base URLs

Paswad runs on a small set of stable hosts. Point your OIDC client at the discovery document and it will pick up the rest automatically.

HostUsed for
id.paswad.comLogin UI & the /authorize endpoint — where the user signs in with their passkey. Also the OIDC issuer.
api.paswad.comAPI surface: token exchange, user info, OIDC discovery, and JWKS.
console.paswad.comDeveloper Console — create apps, manage redirect URIs, webhooks, and secrets.

Key URLs

DocumentURL
OIDC discoveryhttps://api.paswad.com/.well-known/openid-configuration
JWKS (signing keys)https://api.paswad.com/.well-known/jwks.json
Issuer (iss)https://id.paswad.com
Discovery first. Rather than hard-coding endpoint paths, fetch the discovery document once and read authorization_endpoint, token_endpoint, userinfo_endpoint, and jwks_uri from it. Most OIDC libraries do this for you.

Quickstart

Register your application once, then wire up the redirect flow.

  1. Create an app in the Console. Go to console.paswad.com and create a new application. You will receive a client_id (public) and a client_secret (confidential — store it server-side only).
  2. Register your redirect URIs. Add every exact callback URL your app uses, e.g. https://yourapp.com/auth/callback. Redirect URIs are matched exactly — protocol, host, port, and path all have to match a registered entry.
  3. Pick your scopes. Start with openid profile email. See the scopes table below.
  4. Send users through the flow. Redirect to the authorization endpoint, exchange the returned code for tokens, then read the user info.
Illustrative values. Every client_id, secret, code, and token shown in this guide is a placeholder. Replace them with your real values from the Console. Never commit a client_secret to source control or expose it in browser code.

Authorization Code + PKCE

Generate a PKCE code_verifier (a high-entropy random string) and its code_challenge (the base64url-encoded SHA-256 of the verifier). Keep the verifier; send the challenge. Also generate a random state (CSRF protection) and nonce (replay protection for the ID token), and store them in the user’s session.

Step 1 — redirect the browser to
GET https://id.paswad.com/authorize
  ?response_type=code
  &client_id=app_live_a1b2c3
  &redirect_uri=https://yourapp.com/auth/callback
  &scope=openid%20profile%20email
  &state=xyz_random_state
  &nonce=n_random_nonce
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256

The user signs in with their passkey and approves the requested scopes. Paswad then redirects the browser back to your redirect_uri with a short-lived authorization code and the state you sent:

Step 2 — Paswad redirects back
https://yourapp.com/auth/callback
  ?code=ac_8f3d0b1e2c
  &state=xyz_random_state
Verify the state. Confirm the returned state matches the value you stored before exchanging the code. If it does not, abort.

Token exchange

Exchange the authorization code for tokens by POSTing to the token endpoint from your server. Include the original code_verifier — Paswad re-derives the challenge and checks it against the one from step 1.

curl
curl -X POST https://api.paswad.com/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d code=ac_8f3d0b1e2c \
  -d redirect_uri=https://yourapp.com/auth/callback \
  -d client_id=app_live_a1b2c3 \
  -d client_secret=sk_live_REPLACE_ME \
  -d code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
200 OK — response
{
  "access_token": "at_live_b7c1...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "rt_live_9a2f...",
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
  "scope": "openid profile email"
}

Node.js example

Node — exchange code for tokens
const res = await fetch("https://api.paswad.com/v1/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code,                        // from the callback query
    redirect_uri: "https://yourapp.com/auth/callback",
    client_id: process.env.PASWAD_CLIENT_ID,
    client_secret: process.env.PASWAD_CLIENT_SECRET,
    code_verifier: codeVerifier, // the one you stored in step 1
  }),
});

const tokens = await res.json();
// tokens.access_token, tokens.id_token, tokens.refresh_token
Short-lived tokens. Access tokens are short-lived (illustratively expires_in: 900 seconds). Use the refresh_token to obtain a new access token without sending the user through sign-in again, via grant_type=refresh_token at the same token endpoint. Validate the id_token and confirm its nonce matches what you sent.

User info

With a valid access token you can fetch the authenticated user’s profile from the OIDC UserInfo endpoint. The claims returned depend on the scopes you were granted.

curl
curl https://api.paswad.com/v1/oauth/userinfo \
  -H "Authorization: Bearer at_live_b7c1..."
200 OK — response
{
  "sub": "usr_2Hk9c1qZ",
  "name": "Ada Lovelace",
  "email": "[email protected]",
  "email_verified": true
}
Use sub as the user key. The sub claim is the stable, unique identifier for the user. Key your own records on it — email addresses can change.

Scopes

Request the least you need. These standard scopes are available to every app by default:

ScopeGrants access to
openidRequired for OIDC. Returns an id_token and the sub identifier.
profileBasic profile claims, e.g. name.
emailThe user’s email and email_verified flag.
Higher scopes. Sensitive scopes (such as verified passport claims or transaction signing) require review and explicit approval for your app in the Console. Request them from your application’s settings.

Verifying tokens (JWKS)

ID tokens are signed JWTs. Verify them against Paswad’s public signing keys, published as a JWKS. Most OIDC libraries fetch and cache this for you automatically when you point them at the discovery document.

OIDC discovery
GET https://api.paswad.com/.well-known/openid-configuration
JWKS (public signing keys)
GET https://api.paswad.com/.well-known/jwks.json

When validating an id_token, check that:

  • the signature verifies against the JWKS key matching the token’s kid;
  • iss equals https://id.paswad.com;
  • aud equals your client_id;
  • exp is in the future and nonce matches what you sent.
Node — verify with jose
import * as jose from "jose";

const JWKS = jose.createRemoteJWKSet(
  new URL("https://api.paswad.com/.well-known/jwks.json")
);

const { payload } = await jose.jwtVerify(idToken, JWKS, {
  issuer: "https://id.paswad.com",
  audience: process.env.PASWAD_CLIENT_ID,
});
// payload.sub, payload.email, payload.nonce ...

Errors

Failed requests return a JSON envelope with a stable, machine-readable error code and a human-readable error_description. OAuth endpoints follow the standard OAuth 2.0 / OIDC error shape.

Error envelope
{
  "error": "invalid_grant",
  "error_description": "The authorization code is expired or already used.",
  "request_id": "req_5fK0pQ"
}

Log the request_id — it helps when you contact support. Common HTTP statuses:

StatusMeaning
400 Bad RequestMalformed or missing parameter — e.g. a bad code_verifier, unsupported grant_type, or invalid scope. Returns invalid_request / invalid_grant.
401 UnauthorizedClient authentication failed (wrong client_id/client_secret) or the access token is missing, expired, or invalid (invalid_token) on userinfo.
403 ForbiddenThe token is valid but lacks the scope required for the request, or the app is not approved for it (insufficient_scope / access_denied).
404 Not FoundUnknown endpoint or resource. Check you are using the right host (id. vs api.) and path.
429 Too Many RequestsRate limit exceeded. Honour the Retry-After header and back off. See Rate limits.
500 Server ErrorAn unexpected error on Paswad’s side. Retry with backoff; if it persists, contact support with the request_id.

Rate limits

All endpoints are rate-limited to keep the platform fast and fair. Limits are applied per application and may differ by endpoint. When you exceed a limit you receive a 429 Too Many Requests with a Retry-After header telling you how many seconds to wait before retrying.

Every response also carries informational headers so you can pace yourself before hitting the limit:

HeaderMeaning
RateLimit-LimitThe maximum number of requests in the current window.
RateLimit-RemainingRequests left in the current window.
RateLimit-ResetSeconds until the window resets.
Retry-AfterOn a 429, seconds to wait before retrying.
Back off, don’t hammer. On a 429, wait for Retry-After and use exponential backoff with jitter for repeated failures. Cache the discovery document and JWKS rather than fetching them on every request.

Sessions & logout

Tokens are intentionally short-lived. An access_token expires quickly (illustratively in minutes), and your app keeps the user signed in by quietly exchanging the refresh_token for a fresh one when needed. This keeps a leaked access token useful only briefly.

Signing a user out

  • Local sign-out. Clear the user’s session in your own app and discard the tokens you stored. This logs them out of your app.
  • Token revocation. To invalidate a refresh_token (or access token) immediately — for example on “log out everywhere” — revoke it so it can no longer be exchanged. The discovery document advertises the revocation endpoint.
  • RP-initiated logout. To also end the user’s Paswad session, send them to the OIDC end-session (RP-initiated logout) endpoint advertised in discovery, with your post_logout_redirect_uri registered in the Console.
Listen for revocation. Subscribe to the session.revoked and consent.revoked webhooks so your app reacts when a user signs out or removes access from another device.

Webhooks

Subscribe to events to keep your app in sync with identity changes. Add a webhook endpoint in the Console and select the events you care about. Every delivery is an HTTP POST with a JSON body and is HMAC-signed so you can verify it came from Paswad.

EventFires when
user.signed_inA user completes a passkey sign-in to your app.
payment.signedA user approves (signs) a transaction request.
passport.updatedA user’s verified profile claims change.
session.revokedA session is revoked (sign-out, device removal, or admin action).
consent.revokedA user revokes your app’s access.
Example delivery body
{
  "id": "evt_4tP0aZ",
  "type": "user.signed_in",
  "created": 1773600000,
  "data": {
    "sub": "usr_2Hk9c1qZ",
    "client_id": "app_live_a1b2c3"
  }
}
Verify the signature. Each delivery carries a Paswad-Signature header: an HMAC-SHA256 of the raw request body keyed with your webhook signing secret (from the Console). Recompute it over the raw body and compare with a constant-time check before trusting the event.

Security best practices

Paswad is secure by default, but the integration is a shared responsibility. Run through this checklist before you ship:

  • Always use PKCE with S256. Never plain. Generate a fresh code_verifier per authorization request.
  • Validate state and nonce. Reject the callback if state does not match what you stored; reject the ID token if nonce does not.
  • Verify the ID token signature. Check the JWKS signature plus iss, aud, and exp — see Verifying tokens.
  • Keep client_secret server-side only. Never ship it in browser, mobile, or SPA code. Do the token exchange on your backend.
  • Register exact redirect URIs. No wildcards. Match protocol, host, port, and path.
  • Use HTTPS everywhere. For your redirect URIs, webhook endpoints, and all API calls.
  • Rotate secrets. Rotate client_secret and webhook signing secrets periodically and immediately if you suspect exposure.
  • Verify webhook signatures. Constant-time compare the Paswad-Signature header before acting on a delivery.

SDKs & libraries

Because Paswad is a standards-compliant OAuth 2.1 + OpenID Connect provider, you do not need a Paswad-specific SDK. Any certified OIDC client library works — just point it at the discovery document. Some widely used options:

LibraryUse it for
openid-client (Node)Full OIDC client: discovery, PKCE, code exchange, and token validation.
jose (Node)Verifying and decoding JWTs against a remote JWKS — see the verify example.
Standard OIDC libs (any language)Java, Python, Go, and others ship certified OIDC clients that consume the discovery document directly.
Prefer certified clients. Use an OpenID-certified library where possible — it handles PKCE, state/nonce, and signature validation correctly so you do not have to reimplement them.

Integration guides

Because Paswad is a standards-compliant OAuth 2.1 / OpenID Connect provider, it works as an external identity (SSO) provider for any app that speaks OIDC or generic OAuth 2. The pattern is always the same: create an OAuth app in the Paswad Console, register the exact callback URL the platform expects, copy the client_id / client_secret, and point the platform at Paswad’s endpoints. Use these hosts everywhere:

SettingValue
Authorization endpointhttps://id.paswad.com/authorize
Token endpointhttps://api.paswad.com/v1/oauth/token
UserInfo endpointhttps://api.paswad.com/v1/oauth/userinfo
OIDC discoveryhttps://api.paswad.com/.well-known/openid-configuration
JWKShttps://api.paswad.com/.well-known/jwks.json
Issuer (iss)https://id.paswad.com
Scopesopenid profile email
Get credentialshttps://console.paswad.com
Exact redirect URIs. Every callback URL below must be registered exactly in the Paswad Console — protocol, host, port, and path. Replace <your-host> placeholders with your real domain. All client_id / client_secret values shown are illustrative placeholders.

Invoice Ninja

Invoicing & billing

Open Console ↗

Invoice Ninja supports social login over OAuth/OIDC. Wire it to Paswad as a generic OpenID Connect provider.

Redirect URI https://<your-invoice-ninja>/auth/{provider}/callback
  1. In the Paswad Console, create an OAuth app and note the client_id (e.g. app_live_xxx) and client_secret (illustrative).
  2. Register Invoice Ninja's callback as the redirect URI in the Console, e.g. https://<your-invoice-ninja>/auth/{provider}/callback (substitute the provider slug Invoice Ninja assigns).
  3. Point Invoice Ninja's OpenID/OAuth config at the discovery URL https://api.paswad.com/.well-known/openid-configuration, authorize https://id.paswad.com/authorize, token https://api.paswad.com/v1/oauth/token.
  4. Paste the client_id / client_secret and set scopes to openid profile email.
  5. Save and test the "Sign in with Paswad" button on the login screen.

Plugins

Paswad ships official and community plugins so you can add “Sign in with Paswad” to popular platforms without writing any OAuth code by hand. Every plugin is a thin wrapper around the same OAuth 2.1 / OpenID Connect endpoints documented above — they handle the redirect, the PKCE code exchange, and the id_token validation for you, so you just paste a client_id / client_secret from the Paswad Console and go.

Paswad for WordPress

Paswad for WordPress

Official plugin · CMS & publishing

Add a “Sign in with Paswad” button to your WordPress login screen. Your visitors authenticate with a passkey on Paswad and are signed straight into WordPress — no passwords, no extra accounts to manage. The plugin wires WordPress to Paswad’s OAuth 2.1 / OIDC endpoints for you. There are two ways to install it.

A
Install from the WordPress Plugin Directory
  1. In wp-admin, go to Plugins → Add New.
  2. Search for “Paswad” in the plugin search box.
  3. Click Install Now, then Activate.
  4. Go to Settings → Paswad. Paste the client_id and client_secret from the Paswad Console.
  5. Copy the redirect / callback URL the settings screen shows and register it exactly in the Console, then Save.
  6. A “Sign in with Paswad” button now appears on your wp-login screen.
B
Download the plugin (offline / manual install)

Available soon The downloadable plugin build is being finalised and the .zip will be published here shortly. The button and steps below are ready — bookmark this page and check back.

Download paswad-wordpress.zip
  1. Download the paswad-wordpress.zip file (link above).
  2. In wp-admin, go to Plugins → Add New → Upload Plugin.
  3. Choose the .zip, click Install Now, then Activate.
  4. Configure it under Settings → Paswad exactly as in path A: paste your client_id / client_secret, register the callback URL it shows, and save.
Connection settings (illustrative credentials)
SettingValue
Authorization endpointhttps://id.paswad.com/authorize
Token endpointhttps://api.paswad.com/v1/oauth/token
UserInfo endpointhttps://api.paswad.com/v1/oauth/userinfo
OIDC discoveryhttps://api.paswad.com/.well-known/openid-configuration
Scopesopenid profile email
client_idapp_live_a1b2c3 (illustrative)
client_secretsk_live_REPLACE_ME (illustrative)
Get credentialshttps://console.paswad.com

Other plugins

Native plugins for more platforms are on the way. Until one ships, you can connect any of these today via the generic OIDC integration guides above — Paswad is a standard OpenID Connect provider, so any platform that speaks OIDC or OAuth 2 can sign in with Paswad now.

Want a plugin we don’t list? You never have to wait for a native plugin — every platform that supports OpenID Connect or generic OAuth 2 can use Paswad today. See the Integration guides for step-by-step setup, or build directly against the Authorization Code + PKCE flow.

Endpoint reference

The complete public integration surface:

PurposeMethod & URL
AuthorizationGET https://id.paswad.com/authorize
Token exchange / refreshPOST https://api.paswad.com/v1/oauth/token
User infoGET https://api.paswad.com/v1/oauth/userinfo
OIDC discoveryGET https://api.paswad.com/.well-known/openid-configuration
JWKSGET https://api.paswad.com/.well-known/jwks.json

Ready to build?

Create your application and grab your keys in the Developer Console.

Open the Console