@huatek/ghostkey-server
v0.1.8
Published
Backend SDK for GhostKey Auth.
Readme
@huatek/ghostkey-server
Backend SDK for GhostKey Auth.
Use this package to add passwordless authentication to Express, Fastify, NestJS or any Node.js backend. It provides GhostKeyAuth, WebAuthn/passkey registration and login, contextual risk checks, revocable sessions, rotating refresh tokens, recovery flows, step-up authentication, magic-link fallback and middleware helpers.
Install
npm install @huatek/ghostkey-server @huatek/ghostkey-risk-engine @huatek/ghostkey-prisma-adapterBasic setup
import { GhostKeyAuth, createMagicLinkEmailDeliveryFromEnv } from "@huatek/ghostkey-server";
import { createRiskEngine } from "@huatek/ghostkey-risk-engine";
import { createPrismaAdapter } from "@huatek/ghostkey-prisma-adapter";
export const auth = new GhostKeyAuth({
rpId: "localhost",
rpName: "Acme App",
origin: "http://localhost:3000",
adapter: createPrismaAdapter(prisma),
riskEngine: createRiskEngine(),
magicLink: createMagicLinkEmailDeliveryFromEnv(),
challengeTtlSeconds: 120,
webAuthn: { requireUserVerification: false },
session: {
accessTokenTtlSeconds: 900,
refreshTokenTtlSeconds: 60 * 60 * 24 * 30
}
});Main methods
registerUser()createRegistrationOptions()completeRegistration()createAuthenticationOptions()login()logout()validateSession()renegotiateSessionBinding()getSessionGraph()createBotChallenge()verifyBotChallenge()assessAccountProtection()delegateAccess()acceptDelegatedAccess()rotateRefreshToken()revokeDevice()approveNewDevice()listDevices()createMagicLink()loginWithMagicLink()createPasskeyFallbackMagicLink()createStepUpChallenge()verifyStepUp()evaluatePolicy()getSecurityOverview()calculateRisk()auditLog()
Middleware
import {
ghostKeyExpressMiddleware,
ghostKeyFastifyPlugin,
createGhostKeyNestGuard
} from "@huatek/ghostkey-server";GhostKey does not host routes for you. Your app creates HTTP endpoints and calls GhostKeyAuth inside them.
Trusted sessions and continuous auth
Passkey login is account-bound. A valid assertion from [email protected] cannot authenticate [email protected]; GhostKey checks the stored challenge user and credential device owner before creating a session. Mismatches fail with CREDENTIAL_USER_MISMATCH or CHALLENGE_USER_MISMATCH and emit login.credential_mismatch.
Call validateSession() during protected requests to re-evaluate trust after login:
const continuous = await auth.validateSession({
sessionId,
deviceId: currentDeviceId,
context: GhostKeyAuth.contextFromRequest(req)
});It detects IP changes, user-agent changes, suspicious behavior and device binding failures. Possible decisions are allow, allow_with_alert, require_step_up and revoke.
Sessions are bound to the login device. If session.deviceId !== currentDeviceId, GhostKey revokes by default and emits session.device_binding_failed.
For softer UX, configure revokeOnDeviceMismatch: false. Then validateSession() returns require_step_up; after a fresh passkey challenge, call renegotiateSessionBinding() to rebind the session to the verified device/current context.
Session graph
const graph = await auth.getSessionGraph(userId);The graph links sessions by shared device, IP, user-agent, refresh-token family and delegation. Use graph.risk.suspectedLateralHijack to flag corporate account takeover patterns.
Native anti-bot
const challenge = await auth.createBotChallenge({ userId });
const result = await auth.verifyBotChallenge({ challengeId, nonce, honeypot, behaviorRiskScore });This gives you lightweight proof-of-work, honeypot and behavioral detection without external captcha providers.
Account protection
const protection = await auth.assessAccountProtection({ userId, context });Decisions are allow, require_bot_proof, throttle and lock.
Session delegation
const grant = await auth.delegateAccess(userId, {
expiresIn: "1h",
permissions: ["read"]
});The recipient calls acceptDelegatedAccess() with the challenge and token to create a short-lived delegated session.
Passkey fallback
Use createAuthenticationOptions({ registerIfNoPasskey: true }) to let a login flow register the first passkey before creating a session.
If browser passkey registration fails, call:
await auth.createPasskeyFallbackMagicLink({
userId,
reason: "passkey_registration_failed",
context
});If magicLink.deliver is not configured, the result uses delivery.mode: "manual" and includes a token/link for controlled local fallback.
Magic-link email delivery
GhostKey ships a SMTP email helper for magic-links. Configure it through .env:
GHOSTKEY_MAGIC_LINK_URL=https://app.example.com/login/magic-link
GHOSTKEY_EMAIL_PROVIDER=gmail
[email protected]
GHOSTKEY_GMAIL_APP_PASSWORD=xxxx xxxx xxxx xxxx
[email protected]Outlook/Microsoft 365:
GHOSTKEY_EMAIL_PROVIDER=outlook
[email protected]
GHOSTKEY_OUTLOOK_PASS=secret
[email protected]Any SMTP service:
GHOSTKEY_EMAIL_PROVIDER=smtp
GHOSTKEY_SMTP_HOST=smtp.example.com
GHOSTKEY_SMTP_PORT=587
GHOSTKEY_SMTP_SECURE=false
GHOSTKEY_SMTP_USER=user-or-api-key
GHOSTKEY_SMTP_PASS=secret
[email protected]Security notes
- Use HTTPS in production.
- Keep
rpIdandoriginexact. - Store refresh tokens in HTTP-only cookies.
- Always rotate refresh tokens.
- Use magic-link only as strong fallback for recoverable risk failures.
