Radon

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

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.

lib/auth.ts
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/invalid

No 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);             // everywhere

sessionsProvider 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, memberships

Events / 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

Next steps

On this page