kw-login
v0.1.0
Published
"Login with Kwasila" SSO SDK for Node.js — OpenID Connect (authorization code + PKCE) in a few lines.
Maintainers
Readme
kw-login
"Login with Kwasila" for Node.js. A tiny SDK that wraps the OpenID Connect authorization-code flow (with PKCE) so you can sign users in with their Kwasila digital ID in a few lines — discovery, redirect, code exchange and ID-token verification are handled for you.
npm install kw-loginRequires Node.js 18+ (uses the global
fetchand Web Crypto). ESM and CommonJS are both supported.
Quick start (Express)
import express from "express";
import { IdLogin } from "kw-login";
const app = express();
// Credentials come from your Kwasila developer portal (Projects → SSO integration).
const idLogin = new IdLogin({
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.CLIENT_SECRET, // omit for public (SPA/mobile) clients
redirectUri: "https://developerapp.com/api/callback",
});
// Load the provider's config + signing keys once, before serving traffic.
await idLogin.init();
// 1) The user clicks "Login with Kwasila" — send them to the provider.
app.get("/api/login", (req, res) => {
res.redirect(idLogin.getLoginUrl());
});
// 2) The callback you registered in the developer portal.
app.get("/api/callback", async (req, res) => {
try {
const tokens = await idLogin.handleCallback(req.url);
const userInfo = tokens.claims(); // { sub, email, name, ... } — verified
// Create/lookup the user in your DB here.
console.log("User authenticated:", userInfo);
res.redirect("/dashboard");
} catch (err) {
res.status(500).send("Authentication failed");
}
});
app.listen(3000);What the SDK does for you
- Discovery — reads
/.well-known/openid-configurationoninit(). - PKCE + CSRF — generates and stores a single-use
state,nonceand PKCE verifier per login, and validates them on callback. - Token exchange — swaps the authorization
code(+ your secret) for tokens. - ID-token verification — checks the signature against the provider's JWKS
and validates
iss,aud,expandnonce.
Configuration
new IdLogin({
clientId: string, // required
clientSecret?: string, // confidential clients (web/backend); omit for public
redirectUri: string, // required — must match a whitelisted URI
issuer?: string, // default: Kwasila production
scope?: string, // default: "openid profile email"
store?: TransactionStore, // default: in-memory (see below)
clockToleranceSec?: number, // default: 5
fetch?: typeof fetch, // default: global fetch
});The default issuer is Kwasila production; pass issuer to target another
environment. Request the organization scope if you need the user's org claims:
new IdLogin({ ..., scope: "openid profile email organization" }).
API
| Method | Description |
| --- | --- |
| await init() | Load discovery + signing keys. Call once at startup. |
| getLoginUrl(options?) | Authorization URL to redirect to (string). |
| createLoginUrl(options?) | Async variant — use with a custom async store. |
| await handleCallback(reqUrl) | Validate + exchange the callback; returns Tokens. |
| await refresh(refreshToken) | Exchange a refresh token for fresh Tokens. |
| await fetchUserInfo(accessToken) | Fetch UserInfo claims. |
| getLogoutUrl(options?) | RP-initiated logout (end-session) URL. |
Tokens exposes claims() (verified ID-token claims), accessToken,
idToken, refreshToken, expiresAt, scope and returnTo.
getLoginUrl/handleCallback accept a returnTo you set on login and read back
off tokens.returnTo — handy for sending the user back where they started.
Stateful redirects & scaling
Between the redirect and the callback the SDK keeps a short-lived transaction
(the PKCE verifier + state). The default MemoryTransactionStore is fine for a
single instance. For multiple instances or serverless, pass a shared store
(Redis, a DB, or a signed cookie) implementing TransactionStore, and use
await createLoginUrl() so the save is awaited.
Errors
All errors extend KwLoginError (with a .code): ConfigError,
NotInitializedError, AuthorizationError, StateError, TokenExchangeError,
IdTokenError.
