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.
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.
| Host | Used for |
|---|---|
id.paswad.com | Login UI & the /authorize endpoint — where the user signs in with their passkey. Also the OIDC issuer. |
api.paswad.com | API surface: token exchange, user info, OIDC discovery, and JWKS. |
console.paswad.com | Developer Console — create apps, manage redirect URIs, webhooks, and secrets. |
Key URLs
| Document | URL |
|---|---|
| OIDC discovery | https://api.paswad.com/.well-known/openid-configuration |
| JWKS (signing keys) | https://api.paswad.com/.well-known/jwks.json |
Issuer (iss) | https://id.paswad.com |
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.
- Create an app in the Console. Go to
console.paswad.com and create a new application. You will
receive a
client_id(public) and aclient_secret(confidential — store it server-side only). - 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. - Pick your scopes. Start with
openid profile email. See the scopes table below. - Send users through the flow. Redirect to the authorization endpoint, exchange the returned code for tokens, then read the user info.
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. 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 -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 {
"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
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 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 https://api.paswad.com/v1/oauth/userinfo \
-H "Authorization: Bearer at_live_b7c1..." {
"sub": "usr_2Hk9c1qZ",
"name": "Ada Lovelace",
"email": "[email protected]",
"email_verified": true
} 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:
| Scope | Grants access to |
|---|---|
openid | Required for OIDC. Returns an id_token and the sub identifier. |
profile | Basic profile claims, e.g. name. |
email | The user’s email and email_verified flag. |
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.
GET https://api.paswad.com/.well-known/openid-configuration 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; issequalshttps://id.paswad.com;audequals yourclient_id;expis in the future andnoncematches what you sent.
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": "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:
| Status | Meaning |
|---|---|
400 Bad Request | Malformed or missing parameter — e.g. a bad code_verifier, unsupported grant_type, or invalid scope. Returns invalid_request / invalid_grant. |
401 Unauthorized | Client authentication failed (wrong client_id/client_secret) or the access token is missing, expired, or invalid (invalid_token) on userinfo. |
403 Forbidden | The 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 Found | Unknown endpoint or resource. Check you are using the right host (id. vs api.) and path. |
429 Too Many Requests | Rate limit exceeded. Honour the Retry-After header and back off. See Rate limits. |
500 Server Error | An 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:
| Header | Meaning |
|---|---|
RateLimit-Limit | The maximum number of requests in the current window. |
RateLimit-Remaining | Requests left in the current window. |
RateLimit-Reset | Seconds until the window resets. |
Retry-After | On a 429, seconds to wait before retrying. |
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_uriregistered in the Console.
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.
| Event | Fires when |
|---|---|
user.signed_in | A user completes a passkey sign-in to your app. |
payment.signed | A user approves (signs) a transaction request. |
passport.updated | A user’s verified profile claims change. |
session.revoked | A session is revoked (sign-out, device removal, or admin action). |
consent.revoked | A user revokes your app’s access. |
{
"id": "evt_4tP0aZ",
"type": "user.signed_in",
"created": 1773600000,
"data": {
"sub": "usr_2Hk9c1qZ",
"client_id": "app_live_a1b2c3"
}
} 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. Neverplain. Generate a freshcode_verifierper authorization request. - Validate
stateandnonce. Reject the callback ifstatedoes not match what you stored; reject the ID token ifnoncedoes not. - Verify the ID token signature. Check the JWKS signature plus
iss,aud, andexp— see Verifying tokens. - Keep
client_secretserver-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_secretand webhook signing secrets periodically and immediately if you suspect exposure. - Verify webhook signatures. Constant-time compare the
Paswad-Signatureheader 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:
| Library | Use 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. |
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:
| Setting | Value |
|---|---|
| Authorization endpoint | https://id.paswad.com/authorize |
| Token endpoint | https://api.paswad.com/v1/oauth/token |
| UserInfo endpoint | https://api.paswad.com/v1/oauth/userinfo |
| OIDC discovery | https://api.paswad.com/.well-known/openid-configuration |
| JWKS | https://api.paswad.com/.well-known/jwks.json |
Issuer (iss) | https://id.paswad.com |
| Scopes | openid profile email |
| Get credentials | https://console.paswad.com |
<your-host> placeholders with your real domain. All
client_id / client_secret values shown are illustrative placeholders. Invoice Ninja
Invoicing & billing
Invoice Ninja supports social login over OAuth/OIDC. Wire it to Paswad as a generic OpenID Connect provider.
https://<your-invoice-ninja>/auth/{provider}/callback - In the Paswad Console, create an OAuth app and note the
client_id(e.g.app_live_xxx) andclient_secret(illustrative). - 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). - Point Invoice Ninja's OpenID/OAuth config at the discovery URL
https://api.paswad.com/.well-known/openid-configuration, authorizehttps://id.paswad.com/authorize, tokenhttps://api.paswad.com/v1/oauth/token. - Paste the
client_id/client_secretand set scopes toopenid profile email. - Save and test the "Sign in with Paswad" button on the login screen.
Moodle
Learning management
Use Moodle's built-in OAuth 2 services with a Custom issuer.
https://<moodle>/admin/oauth2callback.php - In the Paswad Console, create an OAuth app and copy its
client_id/client_secret. - In Moodle open Site administration → Server → OAuth 2 services and create a new Custom service.
- Set Authorization
https://id.paswad.com/authorize, Tokenhttps://api.paswad.com/v1/oauth/token, UserInfohttps://api.paswad.com/v1/oauth/userinfo, discoveryhttps://api.paswad.com/.well-known/openid-configuration. - Paste the
client_id/client_secret; scopesopenid profile email. - Register the callback Moodle shows —
https://<moodle>/admin/oauth2callback.php— as the redirect URI in the Console. - Add field mappings
sub → idnumber,email → email,name → fullname, then enable it for login.
Nextcloud
Files & collaboration
Use the user_oidc or Social Login app.
https://<nextcloud>/apps/user_oidc/code - Register the callback in the Console:
https://<nextcloud>/apps/user_oidc/code(user_oidc) orhttps://<nextcloud>/apps/sociallogin/custom_oidc/paswad(Social Login). - Add a provider in the app and set the discovery URL to
https://api.paswad.com/.well-known/openid-configuration. - Paste the
client_id/client_secret; scopesopenid profile email.
GitLab
DevOps platform
Add Paswad as a generic OpenID Connect OmniAuth provider.
https://<gitlab>/users/auth/openid_connect/callback - Register the callback in the Console:
https://<gitlab>/users/auth/openid_connect/callback. - In
gitlab.rbadd anopenid_connectprovider with issuerhttps://id.paswad.comand discoveryhttps://api.paswad.com/.well-known/openid-configuration. - Set
client_id/client_secret; scopesopenid profile email; reconfigure GitLab.
Grafana
Dashboards & observability
Use the Generic OAuth integration in grafana.ini.
https://<grafana>/login/generic_oauth - Register the callback in the Console:
https://<grafana>/login/generic_oauth. - Under
[auth.generic_oauth]setauth_urlhttps://id.paswad.com/authorize,token_urlhttps://api.paswad.com/v1/oauth/token,api_urlhttps://api.paswad.com/v1/oauth/userinfo. - Set
client_id/client_secretandscopes = openid profile email.
Rocket.Chat
Team messaging
Add a Custom OAuth provider (Admin → OAuth).
https://<rocketchat>/_oauth/paswad - Register the callback in the Console:
https://<rocketchat>/_oauth/paswad. - Set Authorize URL
https://id.paswad.com/authorize, Token URLhttps://api.paswad.com/v1/oauth/token, Identity URLhttps://api.paswad.com/v1/oauth/userinfo. - Paste
client_id/client_secret; scopeopenid profile email.
Mattermost
Team messaging
Use the OpenID Connect / GitLab-style SSO settings.
https://<mattermost>/signup/openid/complete - Register the callback in the Console:
https://<mattermost>/signup/openid/complete. - In System Console → Authentication → OpenID Connect set the discovery URL
https://api.paswad.com/.well-known/openid-configuration. - Paste
client_id/client_secret; scopeopenid profile email.
Discourse
Community forums
Use the openid-connect (or oauth2-basic) plugin.
https://<discourse>/auth/oidc/callback - Register the callback in the Console:
https://<discourse>/auth/oidc/callback. - In plugin settings set the discovery URL
https://api.paswad.com/.well-known/openid-configuration. - Paste
client_id/client_secret; scopeopenid profile email.
Gitea / Forgejo
Self-hosted Git
Add an Authentication Source → OAuth2 → OpenID Connect.
https://<gitea>/user/oauth2/paswad/callback - Register the callback in the Console:
https://<gitea>/user/oauth2/paswad/callback(the source name forms the path). - Set the OpenID Connect Auto Discovery URL to
https://api.paswad.com/.well-known/openid-configuration. - Paste
client_id/client_secret; scopeopenid profile email.
WordPress
CMS & publishing
Use the OpenID Connect Generic plugin.
https://<wordpress>/wp-admin/admin-ajax.php?action=openid-connect-authorize - Register the callback in the Console:
https://<wordpress>/wp-admin/admin-ajax.php?action=openid-connect-authorize. - Set Login URL
https://id.paswad.com/authorize, Token URLhttps://api.paswad.com/v1/oauth/token, UserInfo URLhttps://api.paswad.com/v1/oauth/userinfo. - Paste
client_id/client_secret; scopeopenid profile email.
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.
Install from the WordPress Plugin Directory
- In wp-admin, go to Plugins → Add New.
- Search for “Paswad” in the plugin search box.
- Click Install Now, then Activate.
- Go to Settings → Paswad. Paste the
client_idandclient_secretfrom the Paswad Console. - Copy the redirect / callback URL the settings screen shows and register it exactly in the Console, then Save.
- A “Sign in with Paswad” button now appears on your
wp-loginscreen.
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 the
paswad-wordpress.zipfile (link above). - In wp-admin, go to Plugins → Add New → Upload Plugin.
- Choose the
.zip, click Install Now, then Activate. - 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)
| Setting | Value |
|---|---|
| Authorization endpoint | https://id.paswad.com/authorize |
| Token endpoint | https://api.paswad.com/v1/oauth/token |
| UserInfo endpoint | https://api.paswad.com/v1/oauth/userinfo |
| OIDC discovery | https://api.paswad.com/.well-known/openid-configuration |
| Scopes | openid profile email |
client_id | app_live_a1b2c3 (illustrative) |
client_secret | sk_live_REPLACE_ME (illustrative) |
| Get credentials | https://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.
- ShopifyComing soon
Storefront & customer accounts. For now, integrate via the generic OIDC guide.
- DrupalComing soon
CMS & sites. Until the native module ships, use the generic OIDC guide.
- JoomlaComing soon
CMS & publishing. Connect today via the generic OIDC guide.
- MagentoComing soon
Adobe Commerce storefronts. Use the generic OIDC guide in the meantime.
- GhostComing soon
Publishing & memberships. Integrate now via the generic OIDC guide.
Endpoint reference
The complete public integration surface:
| Purpose | Method & URL |
|---|---|
| Authorization | GET https://id.paswad.com/authorize |
| Token exchange / refresh | POST https://api.paswad.com/v1/oauth/token |
| User info | GET https://api.paswad.com/v1/oauth/userinfo |
| OIDC discovery | GET https://api.paswad.com/.well-known/openid-configuration |
| JWKS | GET 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