@ruvyxa/auth
v1.1.5
Published
Secure sessions and provider-driven authentication for Ruvyxa applications.
Maintainers
Readme
import { createClient } from 'redis'
import {
createAuth,
google,
nodeRedisCommandPort,
redisAuthStore,
redisRateLimitStore,
} from '@ruvyxa/auth'
const redis = nodeRedisCommandPort(await createClient({ url: process.env.REDIS_URL }).connect())
export const auth = createAuth({
secret: process.env.AUTH_SECRET!,
origin: 'https://app.example.com',
store: redisAuthStore(redis),
rateLimitStore: redisRateLimitStore(redis),
providers: {
email: {
type: 'credentials',
authorize: ({ email, password }, request) => verifyUser(email, password),
},
google: google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
},
})Mount the endpoints with one route handler file under basePath (/__ruvyxa/auth by default). The
same file serves them on every host — ruvyxa dev/start, and every deployed build through the
adapters' request handler — so nothing changes between a self-hosted process and a serverless
function. auth.handle(request) is the same dispatch for an application that mounts the endpoints
itself.
// app/__ruvyxa/auth/[...path]/route.ts
import { auth } from '../../../_server/auth.js'
export const { GET, POST } = auth.handlerscreateAuth() refuses the memory stores when NODE_ENV is production (RUV3105): the process
that would keep sessions in its own memory is the one that must not start.
Stores. AuthStore.take() and AuthRateLimitStore.consume() must be atomic: a read-then-write
in the application process lets two concurrent requests both claim one single-use token, or both
pass one rate-limit slot. redisAuthStore(port) and redisRateLimitStore(port) run each as a
single Lua script on the server, so several instances behind one load balancer share one truth. The
package pins no Redis client: build the port with nodeRedisCommandPort(client) for node-redis or
ioredisCommandPort(client) for ioredis, or hand in any object with get, set, del, and
eval. The included memory stores are for tests and development, require { development: true },
and are refused by production builds with RUV3105.
The session cookie is opaque, HttpOnly, SameSite, and Secure on HTTPS. Session and one-time token keys are HMAC-derived. OAuth state is additionally bound to an HttpOnly browser cookie, protocol parameters cannot be overridden, and non-local provider endpoints must use HTTPS.
Account identity is session.user.id — google:${sub}, github:${id} — and never
session.user.email. An address is a claim the identity provider may or may not stand behind, so
session.user.emailVerified records which it was: true from Google only when the profile carried
email_verified: true, and true from GitHub because its /user email is selected from the
addresses GitHub confirmed. Absent means the provider said nothing. Do not link an OAuth login to an
existing account, grant a role from an address domain, or authorize anything on user.email unless
user.emailVerified is true — an unverified address is an address the person signing in chose.
Set onError(error, request) to send full server-side failures to application observability. Public
500 responses remain generic even if that hook fails.
WebAuthn challenge generation and signature/attestation verification are deliberately delegated to a standards-compliant adapter because correct verification depends on RP ID, origin, authenticator policy, and credential persistence. Successful verification enters the same session pipeline.
