Radon Pro
Unlock 50 OAuth providers, passkeys, 2FA, phone OTP, refresh tokens, multi-device sessions, API keys, orgs, and six more framework integrations with a one-time license.
Everything in Getting Started, the free auth methods (email code, magic link, password, Google), and the four core framework integrations is free and MIT-licensed. Radon Pro unlocks the rest.
Pro code lives under @radonsdk/auth/pro/*, so free-tier apps never bundle it.
What Pro unlocks
50 OAuth providers
GitHub, Discord, Microsoft, Apple, and 46 more — one engine.
Passkeys / WebAuthn
Phishing-resistant biometric sign-in.
2FA / TOTP
Authenticator-app second factor.
Phone / SMS OTP
One-time codes over SMS.
Six more frameworks
NestJS, Koa, SvelteKit, Nuxt, Remix, Astro.
Platform primitives
Refresh tokens, sessions, API keys, orgs, GDPR, events.
Licensing
Pro features require a license key. Set it in config (or the RADON_LICENSE_KEY
env var) and call await auth.init() — Radon verifies the key once against
the license service and caches the result for the process lifetime, never
per-request.
export const auth = new Radon({
adapter,
session: { secret: process.env.RADON_SECRET! },
licenseKey: process.env.RADON_LICENSE_KEY, // or set the env var and omit this
providers: {
oauth: { github: { preset: "github", clientId, clientSecret, redirectUri } },
totp: { issuer: "Acme" },
webauthn: { rpID: "acme.com", origin: "https://acme.com" },
},
});
await auth.init(); // verifies the license; throws if missing/invalidNo key, no Pro
Configure a Pro provider without a valid key and auth.init() throws
LicenseRequiredError (with a link to buy one). Pro accessors like auth.totp
throw until a license is confirmed. Keys are short and readable:
RDN-XXXX-XXXX-XXXX, and are domain-locked — a leaked key is useless on
another domain.
Platform primitives
These aren't sign-in methods — they're the pieces you'd otherwise build by hand.
All are configured under providers and reached via the SDK once licensed.
Refresh tokens
Short-lived access token + long-lived refresh token, with rotation on use and reuse detection.
providers: { refresh: { accessTtlSec: 900, refreshTtlMs: 30 * 864e5 } }
const pair = await auth.refresh.issue({ userId, device: { label: "CLI" } });
// pair.accessToken (short JWT) + pair.refreshToken (opaque, rotated on use)
const next = await auth.refresh.refresh(pair.refreshToken); // old token dies
await auth.refresh.revokeAllForUser(userId);Presenting an already-rotated refresh token (theft/replay) revokes the whole
token family and throws RefreshTokenInvalidError.
Multi-device sessions
Database-backed sessions you can list and revoke individually — a real "active
devices" UI and "log out everywhere". Reached via auth.sessionsProvider.
providers: { sessions: {} }
const { token } = await auth.sessionsProvider.create({ userId, device: { label: "Chrome/macOS", ip } });
const devices = await auth.sessionsProvider.listDevices(userId, token); // current one is flagged
await auth.sessionsProvider.revokeDevice(devices[0].id); // one device
await auth.sessionsProvider.revokeAll(userId); // everywheresessionsProvider vs. sessions
auth.sessionsProvider is this Pro provider. Don't confuse it with auth.sessions
— that's the stateless JWT SessionManager used by the free tier.
Guest sessions + upgrade
Create an anonymous user, associate data with it, then attach a real identity later — keeping the same user id and data.
const guest = await auth.sessionsProvider.createGuest({ metadata: { cart } });
// ...later, when they sign up...
await auth.sessionsProvider.upgrade({
userId: guest.user.id, provider: "email", providerAccountId: email, email, emailVerified: true,
});API keys
Service-to-service / CLI keys, tied to a user and/or org.
providers: { apiKeys: {} }
const { key, record } = await auth.apiKeys.create({ userId, name: "CI token", scopes: ["read"] });
// key = "rk_<prefix>_<secret>" — shown ONCE. Only its hash is stored.
const owner = await auth.apiKeys.verify(presentedKey); // throws if invalid/expired/revoked
await auth.apiKeys.revoke(record.id);Orgs / teams
Multi-tenant orgs with roles (owner > admin > member), email invites, and
membership management.
providers: { orgs: { sender: resendSender({ apiKey }), inviteUrl: "https://app.com/invite" } }
const { org } = await auth.orgs.create({ name: "Acme", ownerUserId });
await auth.orgs.invite({ orgId: org.id, email: "new@acme.com", role: "member", invitedByUserId });
const membership = await auth.orgs.acceptInvite(tokenFromEmail, acceptingUserId);
await auth.orgs.setRole(org.id, userId, "admin");The last owner can't be removed or demoted. Use auth.orgs.assertRole(orgId, userId, "admin") to gate your own actions.
Impersonation
Mint a session for another user, for support/debugging.
// ⚠️ Radon does NOT check who is an admin. Verify admin authorization YOURSELF first.
const { token } = await auth.sessionsProvider.impersonate(adminUserId, targetUserId, {
reason: "support ticket #42",
});
// token carries `imp: true` + `act: adminUserId` claims and emits an `impersonation` event.Account export & deletion (GDPR)
providers: { account: {} }
const data = await auth.account.exportData(userId); // all records, secrets stripped
await auth.account.deleteUser(userId); // cascade: sessions, keys, identities, membershipsEvents / webhooks
Hook side effects — analytics, provisioning, audit logs — without forking Radon. Subscribing requires a license; handlers are isolated (a throwing handler never breaks auth).
const off = auth.on("user.created", ({ user }) => analytics.track(user));
auth.on("login", ({ user, method }) => {});
auth.on("session.revoked", ({ userId, all }) => {});
auth.on("refresh.reuse_detected", ({ userId }) => alertSecurity(userId));Available events: user.created, user.updated, user.deleted,
session.created, session.revoked, login, refresh.rotated,
refresh.reuse_detected, apikey.created, apikey.revoked, org.created,
org.member_added, org.member_removed, org.invite_sent, impersonation.
Security notes
- Reversible secrets are encrypted at rest. TOTP secrets use AES-256-GCM
keyed by your
RADON_ENCRYPTION_KEY. Everything else that can be hashed, is. - Adapter capabilities. Pro features need a few extra adapter methods. All
built-in adapters implement them; a custom adapter missing one throws a clear
AdapterCapabilityError.
RADON_ENCRYPTION_KEY=$(openssl rand -hex 32) # required for TOTP