Server SDK
@loginwith/client — verify tokens server-side, any language.
The server SDK is a thin wrapper around JWT verification + the LoginWith REST API. It’s where your backend confirms the bearer token came from LoginWith, figures out who’s calling, and makes authenticated API calls on their behalf.
Install
npm install @loginwith/client
Construct
import { LoginWith } from "@loginwith/client"
const lgw = new LoginWith({
client_id: process.env.LGW_CLIENT_ID!,
client_secret: process.env.LGW_CLIENT_SECRET!,
})
The SDK auto-discovers your issuer and audience from the client_id (both are derived from your org slug on the server side — you don’t need to pass either).
Verify a bearer token
const [caller, error] = await lgw.verify(req.headers.authorization)
if (error) {
// Invalid token, expired, wrong audience, missing kid, etc.
return res.status(401).end()
}
// caller.userId — present on user tokens
// caller.clientId — always set (which client minted the token)
// caller.orgId — the owning org
// caller.scopes — optional array of scopes granted
lgw.verify does all the JWKS lifting (fetch, cache, rotate on kid miss), signature validation (RS256), and standard claim checks (iss, aud, exp, nbf) in one call.
Framework middleware
Rather than calling verify yourself in every handler, drop in the adapter for your framework:
@loginwith/authenticate-koa@loginwith/authenticate-express@loginwith/authenticate-hono@loginwith/authenticate-nest@loginwith/authenticate-fastify@loginwith/authenticate-elysia@loginwith/authenticate-flask,authenticate-fastapi,authenticate-django@loginwith/authenticate-rails,authenticate-laravel,authenticate-phoenix@loginwith/authenticate-ktor,authenticate-axum@loginwith/authenticate-node(barenode:http)
Each exposes an authenticate(lgw, { whitelist }) function that wraps the app, populates ctx.state.caller (or the framework’s equivalent), and short-circuits unauthenticated requests with a 401.
Resource namespaces
Once you have an lgw instance, the REST API is exposed via typed methods:
await lgw.users.me() // the authenticated caller (via async ctx)
await lgw.users.get(userId) // fetch by id
await lgw.users.list({ cursor, limit })
await lgw.orgs.get(orgId)
await lgw.tenants.list({ orgId })
All methods are scoped to your client_id’s org unless the client is the LoginWith console client (cross-org admin surface).