From d8160ed5887ecd3e1de32731acd995df057e2900 Mon Sep 17 00:00:00 2001 From: Hernan Alvarado Date: Sun, 1 Mar 2026 15:26:06 -0500 Subject: [PATCH 1/4] feat(core): read auth configuration from environment variables Support configuration via: - AUTH_DEBUG - AUTH_LOG_LEVEL - SALT - SECRET - TRUSTED_ORIGINS - TRUSTED_PROXY_HEADERS --- packages/core/src/@types/index.ts | 4 +- packages/core/src/env.ts | 26 +++++++++++ packages/core/src/index.ts | 61 ++++++------------------ packages/core/src/jose.ts | 6 +-- packages/core/src/logger.ts | 77 ++++++++++++++++++++++++++++++- packages/core/src/oauth/index.ts | 9 ++-- packages/core/src/utils.ts | 2 + packages/core/test/env.test.ts | 30 ++++++++++++ packages/core/test/presets.ts | 28 ----------- packages/core/vitest.config.ts | 1 + 10 files changed, 157 insertions(+), 87 deletions(-) create mode 100644 packages/core/test/env.test.ts diff --git a/packages/core/src/@types/index.ts b/packages/core/src/@types/index.ts index b408cf80..e74e1ba7 100644 --- a/packages/core/src/@types/index.ts +++ b/packages/core/src/@types/index.ts @@ -186,6 +186,8 @@ export interface AuthConfig { * Misconfiguration can lead to security vulnerabilities, such as incorrect handling of secure cookies or * inaccurate client IP logging. * + * This value can also be set via environment variable as `AURA_AUTH_TRUSTED_PROXY_HEADERS` + * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded @@ -193,7 +195,7 @@ export interface AuthConfig { */ trustedProxyHeaders?: boolean - logger?: Logger + logger?: boolean | Logger /** * Defines trusted origins for your application to prevent open redirect attacks. * URLs from the Referer header, Origin header, request URL, and redirectTo option diff --git a/packages/core/src/env.ts b/packages/core/src/env.ts index 4d61c05c..dd27f931 100644 --- a/packages/core/src/env.ts +++ b/packages/core/src/env.ts @@ -31,3 +31,29 @@ export const env = new Proxy({} as Record, { } }, }) + +export const getEnv = (key: string): string | undefined => { + const keys = [`AURA_AUTH_${key.toUpperCase()}`, `AURA_${key.toUpperCase()}`, `AUTH_${key.toUpperCase()}`, key.toUpperCase()] + return env[keys.find((k) => env[k]) ?? ""] +} + +export const getEnvBoolean = (key: string): boolean => { + const value = getEnv(key) + if (value === undefined) return false + const normalized = value.trim().toLowerCase() + if (["1", "true", "yes", "on", "debug"].includes(normalized)) return true + return false +} + +export const getEnvArray = (key: string, defaultValue: T = []) => { + const value = getEnv(key) + if (!value) return defaultValue ?? [] + return ( + value + .split(/[,;\n]+/) + .map((v) => v.trim()) + .filter(Boolean) ?? + defaultValue ?? + [] + ) +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 345eab88..73783fed 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,11 +1,12 @@ import { createRouter, type RouterConfig } from "@aura-stack/router" import { createJoseInstance } from "@/jose.ts" import { createCookieStore } from "@/cookie.ts" -import { createErrorHandler, useSecureCookies } from "@/utils.ts" +import { createProxyLogger } from "@/logger.ts" +import { getEnvArray, getEnvBoolean } from "@/env.ts" import { createBuiltInOAuthProviders } from "@/oauth/index.ts" +import { createErrorHandler, useSecureCookies } from "@/utils.ts" import { signInAction, callbackAction, sessionAction, signOutAction, csrfTokenAction } from "@/actions/index.ts" -import { createLogEntry, type logMessages } from "@/logger.ts" -import type { AuthConfig, InternalLogger, Logger, LogLevel, SyslogOptions } from "@/@types/index.ts" +import type { AuthConfig } from "@/@types/index.ts" export type { AuthConfig, @@ -27,63 +28,27 @@ export type { export { createClient, type AuthClient, type Client, type ClientOptions } from "@/client.ts" -/** - * Maps LogLevel to Severity hierarchically per RFC 5424. - * Each level includes itself and all more-severe levels. - */ -const logLevelToSeverity: Record = { - debug: ["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"], - info: ["info", "notice", "warning", "error", "critical", "alert", "emergency"], - warn: ["warning", "error", "critical", "alert", "emergency"], - error: ["error", "critical", "alert", "emergency"], -} - -const createLoggerProxy = (logger?: Logger): InternalLogger | undefined => { - if (!logger) return undefined - const level = logger.level - const allowedSeverities = logLevelToSeverity[level] ?? [] - - const internalLogger: InternalLogger = { - level, - log(key: T, overrides?: Partial) { - const entry = createLogEntry(key, overrides) - if (!allowedSeverities.includes(entry.severity)) return entry - - logger.log({ - timestamp: entry.timestamp ?? new Date().toISOString(), - appName: entry.appName ?? "aura-auth", - hostname: entry.hostname ?? "aura-auth", - ...entry, - }) - - return entry - }, - } - return internalLogger -} - const createInternalConfig = (authConfig?: AuthConfig): RouterConfig => { - const useSecure = authConfig?.trustedProxyHeaders ?? false - const logger = authConfig?.logger - const internalLogger = createLoggerProxy(logger) + const useProxyHeaders = getEnvBoolean("TRUSTED_PROXY_HEADERS") || authConfig?.trustedProxyHeaders || false + const logger = createProxyLogger(authConfig) return { basePath: authConfig?.basePath ?? "/auth", - onError: createErrorHandler(internalLogger), + onError: createErrorHandler(logger), context: { oauth: createBuiltInOAuthProviders(authConfig?.oauth), cookies: createCookieStore( - useSecure, + useProxyHeaders, authConfig?.cookies?.prefix, authConfig?.cookies?.overrides ?? {}, - internalLogger + logger ), jose: createJoseInstance(authConfig?.secret), secret: authConfig?.secret, basePath: authConfig?.basePath ?? "/auth", - trustedProxyHeaders: useSecure, - trustedOrigins: authConfig?.trustedOrigins, - logger: internalLogger, + trustedProxyHeaders: useProxyHeaders, + trustedOrigins: getEnvArray("TRUSTED_ORIGINS", authConfig?.trustedOrigins), + logger, }, use: [ (ctx) => { @@ -92,7 +57,7 @@ const createInternalConfig = (authConfig?: AuthConfig): RouterConfig => { useSecure, authConfig?.cookies?.prefix, authConfig?.cookies?.overrides ?? {}, - internalLogger + logger ) ctx.context.cookies = cookies return ctx diff --git a/packages/core/src/jose.ts b/packages/core/src/jose.ts index aecbea2d..c2b34d7a 100644 --- a/packages/core/src/jose.ts +++ b/packages/core/src/jose.ts @@ -1,4 +1,4 @@ -import { env } from "@/env.ts" +import { getEnv } from "@/env.ts" import { createJWT, createJWS, @@ -21,7 +21,7 @@ export { encoder, getRandomBytes, getSubtleCrypto } from "@aura-stack/jose/crypt * @returns jose instance with methods for encoding/decoding JWTs and signing/verifying JWSs */ export const createJoseInstance = (secret?: string) => { - secret ??= env.AURA_AUTH_SECRET! ?? env.AUTH_SECRET! + secret ??= getEnv("SECRET") if (!secret) { throw new AuthInternalError( "JOSE_INITIALIZATION_FAILED", @@ -29,7 +29,7 @@ export const createJoseInstance = (secret?: string) => { ) } - const salt = env.AURA_AUTH_SALT ?? env.AUTH_SALT + const salt = getEnv("SALT") if (!salt) { throw new AuthInternalError( "JOSE_INITIALIZATION_FAILED", diff --git a/packages/core/src/logger.ts b/packages/core/src/logger.ts index 0afdef4e..324913d5 100644 --- a/packages/core/src/logger.ts +++ b/packages/core/src/logger.ts @@ -1,4 +1,6 @@ -import type { SyslogOptions } from "@/@types/index.ts" +import { getEnv, getEnvBoolean } from "./env.ts" +import { createStructuredData } from "./utils.ts" +import type { AuthConfig, InternalLogger, Logger, LogLevel, SyslogOptions } from "@/@types/index.ts" /** * Log message definitions organized by category. @@ -261,11 +263,82 @@ export const logMessages = { }, } as const +// @todo: verify .pid support in Deno and Bun runtime environments export const createLogEntry = (key: T, overrides?: Partial): SyslogOptions => { const message = logMessages[key] - return { ...message, + timestamp: new Date().toISOString(), + hostname: "aura-auth", + procId: process.pid.toString(), ...overrides, } } + +/** + * Maps LogLevel to Severity hierarchically per RFC 5424. + * Each level includes itself and all more-severe levels. + */ +const logLevelToSeverity: Record = { + debug: ["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"], + info: ["info", "notice", "warning", "error", "critical", "alert", "emergency"], + warn: ["warning", "error", "critical", "alert", "emergency"], + error: ["error", "critical", "alert", "emergency"], +} + +const getSeverityLevel = (severity: string): number => { + const severities: Record = { + emergency: 0, + alert: 1, + critical: 2, + error: 3, + warning: 4, + notice: 5, + info: 6, + debug: 7, + } + return severities[severity] ?? 6 +} + +export const createSyslogMessage = (options: SyslogOptions): string => { + const { timestamp, hostname, appName, procId, msgId, structuredData, message } = options + const pri = (options.facility ?? 16) * 8 + getSeverityLevel(options.severity) + const structuredDataStr = createStructuredData(structuredData ?? {}) + return `<${pri}>1 ${timestamp} ${hostname} ${appName} ${procId} ${msgId} ${structuredDataStr} ${message}` +} + +export const createLogger = (logger?: Logger): InternalLogger | undefined => { + if (!logger) return undefined + const level = logger.level + const allowedSeverities = logLevelToSeverity[level] ?? [] + + const internalLogger: InternalLogger = { + level, + log(key: T, overrides?: Partial) { + const entry = createLogEntry(key, overrides) + if (!allowedSeverities.includes(entry.severity)) return entry + + logger.log({ + timestamp: entry.timestamp, + appName: entry.appName ?? "aura-auth", + hostname: entry.hostname ?? "aura-auth", + ...entry, + }) + + return entry + }, + } + return internalLogger +} + +export const createProxyLogger = (config?: AuthConfig) => { + const level = getEnv("LOG_LEVEL") + const debug = getEnvBoolean("DEBUG") + if (debug || Boolean(config?.logger)) { + return createLogger({ + level: (level as LogLevel) ?? "debug", + log: createSyslogMessage, + }) + } + return createLogger(config?.logger as Exclude) +} diff --git a/packages/core/src/oauth/index.ts b/packages/core/src/oauth/index.ts index 25743327..bca16a12 100644 --- a/packages/core/src/oauth/index.ts +++ b/packages/core/src/oauth/index.ts @@ -4,7 +4,7 @@ * This modules re-exports OAuth providers available in Aura Auth to be used in the Auth instance configuration. */ import type { LiteralUnion, OAuthProviderCredentials } from "@/@types/index.ts" -import { env } from "@/env.ts" +import { getEnv } from "@/env.ts" import { github } from "./github.ts" import { bitbucket } from "./bitbucket.ts" import { figma } from "./figma.ts" @@ -47,6 +47,7 @@ export const builtInOAuthProviders = { * Loads OAuth provider credentials from environment variables based on the provider name. * Supported patterns for environment variables are: * - `AURA_AUTH_{OAUTH_PROVIDER}_CLIENT_{ID|SECRET}` + * - `AURA_{OAUTH_PROVIDER}_CLIENT_{ID|SECRET}` * - `AUTH_{OAUTH_PROVIDER}_CLIENT_{ID|SECRET}` * - `{OAUTH_PROVIDER}_CLIENT_{ID|SECRET}` * @@ -54,11 +55,9 @@ export const builtInOAuthProviders = { * @returns The credentials for the specified OAuth provider */ const defineOAuthEnvironment = (oauth: string) => { - const clientIdSuffix = `${oauth.toUpperCase()}_CLIENT_ID` - const clientSecretSuffix = `${oauth.toUpperCase()}_CLIENT_SECRET` const loadEnvs = OAuthEnvSchema.safeParse({ - clientId: env[`AURA_AUTH_${clientIdSuffix}`] ?? env[`AUTH_${clientIdSuffix}`] ?? env[`${clientIdSuffix}`], - clientSecret: env[`AURA_AUTH_${clientSecretSuffix}`] ?? env[`AUTH_${clientSecretSuffix}`] ?? env[`${clientSecretSuffix}`], + clientId: getEnv(`${oauth.toUpperCase()}_CLIENT_ID`), + clientSecret: getEnv(`${oauth.toUpperCase()}_CLIENT_SECRET`), }) if (!loadEnvs.success) { const msg = JSON.stringify(formatZodError(loadEnvs.error), null, 2) diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 18335522..5d927b7b 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -155,3 +155,5 @@ export const getErrorName = (error: unknown): string => { } return typeof error === "string" ? error : "UnknownError" } + +export const getEnvironmentVariables = () => {} diff --git a/packages/core/test/env.test.ts b/packages/core/test/env.test.ts new file mode 100644 index 00000000..d42c6340 --- /dev/null +++ b/packages/core/test/env.test.ts @@ -0,0 +1,30 @@ +import { afterEach, describe, expect, test, vi } from "vitest" +import { env } from "@/env.ts" +import { generateSecure } from "@/secure.ts" + +describe("env", () => { + afterEach(() => { + vi.unstubAllEnvs() + }) + + test("read AURA_AUTH_SECRET from environment", () => { + const secret = generateSecure() + vi.stubEnv("AURA_AUTH_SECRET", secret!) + expect(env.AURA_AUTH_SECRET).toBe(secret) + }) + + test("read multiple secrets from environment", () => { + const secret1 = generateSecure() + const secret2 = generateSecure() + vi.stubEnv("AURA_AUTH_SECRET", `${secret1},${secret2}`) + expect(env.AURA_AUTH_SECRET).toBe(`${secret1},${secret2}`) + }) + + test("nose", () => { + const secret1 = generateSecure() + const secret2 = generateSecure() + vi.stubEnv("AURA_AUTH_SECRET", secret1) + vi.stubEnv("AURA_AUTH_SECRET", secret2) + expect(env.AURA_AUTH_SECRET).toBe(secret2) + }) +}) diff --git a/packages/core/test/presets.ts b/packages/core/test/presets.ts index 0e340f4e..bc6c8b0d 100644 --- a/packages/core/test/presets.ts +++ b/packages/core/test/presets.ts @@ -1,5 +1,4 @@ import { createAuth } from "@/index.ts" -import { createStructuredData } from "@/utils.ts" import type { OAuthProviderCredentials } from "@/@types/index.ts" import type { JWTPayload } from "@/jose.ts" @@ -38,36 +37,9 @@ export const sessionPayload: JWTPayload = { image: "https://example.com/image.jpg", } -const severityToSyslogSeverity = (severity: string): number => { - const obj: Record = { - emergency: 0, - alert: 1, - critical: 2, - error: 3, - warning: 4, - notice: 5, - info: 6, - debug: 7, - } - return obj[severity] ?? 6 -} - export const { handlers: { GET, POST }, jose, } = createAuth({ oauth: [oauthCustomService, oauthCustomServiceProfile], - cookies: {}, - logger: { - level: "debug", - log({ facility, severity, timestamp, message, structuredData, msgId }) { - const pri = facility * 8 + severityToSyslogSeverity(severity) - /** - * This is not a real logger implementation. - * Replace this with your own logger implementation. - */ - const msg = createStructuredData(structuredData ?? {}) - console.log(`<${pri}>1 ${timestamp} aura-auth - - ${msgId} ${msg} ${message}`) - }, - }, }) diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts index 5e7df530..a629a046 100644 --- a/packages/core/vitest.config.ts +++ b/packages/core/vitest.config.ts @@ -24,6 +24,7 @@ export default defineConfig({ "AURA_AUTH_OAUTH-PROVIDER_CLIENT_SECRET": "oauth_client_secret", "AURA_AUTH_OAUTH-PROFILE_CLIENT_ID": "oauth_profile_client_id", "AURA_AUTH_OAUTH-PROFILE_CLIENT_SECRET": "oauth_profile_client_secret", + AURA_AUTH_DEBUG: "1", }, }, resolve: { From c46024ad80e40257acd32e08ad6a4be3a71d3a10 Mon Sep 17 00:00:00 2001 From: Hernan Alvarado Date: Sun, 1 Mar 2026 15:41:40 -0500 Subject: [PATCH 2/4] chore: apply coderabbit --- packages/core/src/env.ts | 16 ++++++---------- packages/core/src/index.ts | 8 +++++--- packages/core/src/logger.ts | 7 +++---- packages/core/src/utils.ts | 2 -- packages/core/test/env.test.ts | 2 +- 5 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/core/src/env.ts b/packages/core/src/env.ts index dd27f931..19f37a0a 100644 --- a/packages/core/src/env.ts +++ b/packages/core/src/env.ts @@ -45,15 +45,11 @@ export const getEnvBoolean = (key: string): boolean => { return false } -export const getEnvArray = (key: string, defaultValue: T = []) => { +export const getEnvArray = (key: string, defaultValue: string[] = []) => { const value = getEnv(key) - if (!value) return defaultValue ?? [] - return ( - value - .split(/[,;\n]+/) - .map((v) => v.trim()) - .filter(Boolean) ?? - defaultValue ?? - [] - ) + if (!value) return defaultValue + return value + .split(/[,;\n]+/) + .map((v) => v.trim()) + .filter(Boolean) } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 73783fed..478221f4 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,7 +2,7 @@ import { createRouter, type RouterConfig } from "@aura-stack/router" import { createJoseInstance } from "@/jose.ts" import { createCookieStore } from "@/cookie.ts" import { createProxyLogger } from "@/logger.ts" -import { getEnvArray, getEnvBoolean } from "@/env.ts" +import { getEnv, getEnvArray, getEnvBoolean } from "@/env.ts" import { createBuiltInOAuthProviders } from "@/oauth/index.ts" import { createErrorHandler, useSecureCookies } from "@/utils.ts" import { signInAction, callbackAction, sessionAction, signOutAction, csrfTokenAction } from "@/actions/index.ts" @@ -29,7 +29,9 @@ export type { export { createClient, type AuthClient, type Client, type ClientOptions } from "@/client.ts" const createInternalConfig = (authConfig?: AuthConfig): RouterConfig => { - const useProxyHeaders = getEnvBoolean("TRUSTED_PROXY_HEADERS") || authConfig?.trustedProxyHeaders || false + const trustedProxyHeadersEnv = getEnv("TRUSTED_PROXY_HEADERS") + const useProxyHeaders = + trustedProxyHeadersEnv === undefined ? (authConfig?.trustedProxyHeaders ?? false) : getEnvBoolean("TRUSTED_PROXY_HEADERS") const logger = createProxyLogger(authConfig) return { @@ -47,7 +49,7 @@ const createInternalConfig = (authConfig?: AuthConfig): RouterConfig => { secret: authConfig?.secret, basePath: authConfig?.basePath ?? "/auth", trustedProxyHeaders: useProxyHeaders, - trustedOrigins: getEnvArray("TRUSTED_ORIGINS", authConfig?.trustedOrigins), + trustedOrigins: getEnvArray("TRUSTED_ORIGINS") ?? authConfig?.trustedOrigins, logger, }, use: [ diff --git a/packages/core/src/logger.ts b/packages/core/src/logger.ts index 324913d5..87c6627f 100644 --- a/packages/core/src/logger.ts +++ b/packages/core/src/logger.ts @@ -263,14 +263,13 @@ export const logMessages = { }, } as const -// @todo: verify .pid support in Deno and Bun runtime environments export const createLogEntry = (key: T, overrides?: Partial): SyslogOptions => { const message = logMessages[key] return { ...message, timestamp: new Date().toISOString(), hostname: "aura-auth", - procId: process.pid.toString(), + procId: typeof process !== "undefined" && process.pid ? process.pid.toString() : "-", ...overrides, } } @@ -301,7 +300,7 @@ const getSeverityLevel = (severity: string): number => { } export const createSyslogMessage = (options: SyslogOptions): string => { - const { timestamp, hostname, appName, procId, msgId, structuredData, message } = options + const { timestamp, hostname, appName = "aura-auth", procId = "-", msgId, structuredData, message } = options const pri = (options.facility ?? 16) * 8 + getSeverityLevel(options.severity) const structuredDataStr = createStructuredData(structuredData ?? {}) return `<${pri}>1 ${timestamp} ${hostname} ${appName} ${procId} ${msgId} ${structuredDataStr} ${message}` @@ -340,5 +339,5 @@ export const createProxyLogger = (config?: AuthConfig) => { log: createSyslogMessage, }) } - return createLogger(config?.logger as Exclude) + return typeof config?.logger === "object" ? createLogger(config.logger) : undefined } diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 5d927b7b..18335522 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -155,5 +155,3 @@ export const getErrorName = (error: unknown): string => { } return typeof error === "string" ? error : "UnknownError" } - -export const getEnvironmentVariables = () => {} diff --git a/packages/core/test/env.test.ts b/packages/core/test/env.test.ts index d42c6340..44b1d560 100644 --- a/packages/core/test/env.test.ts +++ b/packages/core/test/env.test.ts @@ -20,7 +20,7 @@ describe("env", () => { expect(env.AURA_AUTH_SECRET).toBe(`${secret1},${secret2}`) }) - test("nose", () => { + test("last stubbed value overwrites previous values", () => { const secret1 = generateSecure() const secret2 = generateSecure() vi.stubEnv("AURA_AUTH_SECRET", secret1) From a5c4dc05434135695b51febc9f878bd7afda4983 Mon Sep 17 00:00:00 2001 From: Hernan Alvarado Date: Sun, 1 Mar 2026 15:57:51 -0500 Subject: [PATCH 3/4] docs(core): update environment variables content --- docs/src/content/docs/api-reference/core.mdx | 29 ++++-- docs/src/content/docs/configuration/env.mdx | 96 +++++++++++--------- packages/core/CHANGELOG.md | 4 +- 3 files changed, 77 insertions(+), 52 deletions(-) diff --git a/docs/src/content/docs/api-reference/core.mdx b/docs/src/content/docs/api-reference/core.mdx index dda3200b..71cebc5c 100644 --- a/docs/src/content/docs/api-reference/core.mdx +++ b/docs/src/content/docs/api-reference/core.mdx @@ -527,14 +527,26 @@ import { discord, type DiscordProfile, type Nameplate } from "@aura-stack/auth/o ## Environment Variables -Aura Auth required some environment variables to create the OAuth flow and security measures. The environments required are: +Aura Auth requires environment variables to create the OAuth flow and enforce security controls. -- `AURA_AUTH_SECRET`: opaque value used to sign and encrypt user's session -- `AURA_AUTH_SALT`: Salt value used for key derivation to sign and encrypt JWTs and CSRF tokens. -- `AURA_AUTH_{PROVIDER}_CLIENT_ID` -- `AURA_AUTH-{PROVIDER}_CLIENT_SECRET` +For any `{KEY}`, Aura Auth checks the following names in order and uses the first non-empty value: -> Aura Auth loads the environment variables that follows the previous patterns. +1. `AURA_AUTH_{KEY}` +2. `AURA_{KEY}` +3. `AUTH_{KEY}` +4. `{KEY}` + +Common variables are: + +- `SECRET`: opaque value used to sign and encrypt user sessions +- `SALT`: salt value used for key derivation when signing/encrypting JWTs and CSRF tokens +- `{PROVIDER}_CLIENT_ID` +- `{PROVIDER}_CLIENT_SECRET` +- `TRUSTED_ORIGINS`: comma/semicolon/newline-separated list of trusted origins +- `DEBUG`: enables debug mode when set to `1`, `true`, `yes`, `on`, or `debug` +- `LOG_LEVEL`: built-in logger level (`debug`, `info`, `warn`, `error`) + +> `APP_NAME` is not automatically loaded as an environment variable by the core runtime. Set `appName` from a custom logger if you need a custom syslog app name. ### Example @@ -552,6 +564,11 @@ AURA_AUTH_GITHUB_CLIENT_SECRET=0123456789abcdef... # Custom Provider AURA_AUTH_CUSTOM_CLIENT_ID=your_client_id AURA_AUTH_CUSTOM_CLIENT_SECRET=your_client_secret + +# Runtime behavior +AURA_AUTH_TRUSTED_ORIGINS="https://app.example.com,https://admin.example.com" +AURA_AUTH_DEBUG="1" +AURA_AUTH_LOG_LEVEL="info" ``` Never commit `.env` files to version control. Add them to `.gitignore`. diff --git a/docs/src/content/docs/configuration/env.mdx b/docs/src/content/docs/configuration/env.mdx index 61992be4..0ec72c64 100644 --- a/docs/src/content/docs/configuration/env.mdx +++ b/docs/src/content/docs/configuration/env.mdx @@ -6,28 +6,40 @@ description: Complete guide to configuring Aura Auth via environment variables ## Environment Variables Aura Auth requires environment variables to store sensitive data related to OAuth credentials, secret keys for signing and encrypting JWTs and CSRF tokens, and salts for key derivation. +Additionally, there are environment variables that control runtime behavior of the core auth module, such as trusted origins and debug mode. All environment variables support multiple patterns that allow Aura Auth to load automatically without needing to set -them directly in the `createAuth` function. The `AURA` prefix is optional for environment variables consumed by Aura Auth. +them directly in the `createAuth` function. + +When Aura Auth resolves a variable key, it checks the following names in order and uses the first non-empty value: + +1. `AURA_AUTH_{KEY}` +2. `AURA_{KEY}` +3. `AUTH_{KEY}` +4. `{KEY}` + For more details, read: - [Secure variables](#secure-variables) - [OAuth variables](#oauth-variables) +- [Runtime variables](#runtime-variables) + + All the values of the environment variable is overridden the configuration option in the `createAuth` function. It includes the + `secret`, `trustedOrigins`, and `logger` options. So, if you set the environment variable for these options, it will override + the configuration provided in the `createAuth` function. + + ### Secure Variables -| Name | Description | -| ----------------------------------- | ------------------------------------------------------------------------------------ | -| `AURA_AUTH_SECRET` \| `AUTH_SECRET` | 32-byte secret for JWTs signing/encryption | -| `AURA_AUTH_SALT` \| `AUTH_SALT` | 32-byte salt for key derivation used for signing and encrypting JWTs and CSRF tokens | - - - The `AURA_AUTH_SECRET` environment variable is overridden by `secret` configuration option in the `createAuth` function. - +| Name | Description | +| -------- | ------------------------------------------------------------------------------------ | +| `SECRET` | 32-byte secret for JWTs signing/encryption | +| `SALT` | 32-byte salt for key derivation used for signing and encrypting JWTs and CSRF tokens | #### Generating Secrets and Salts @@ -41,40 +53,31 @@ openssl rand -base64 32 ```bash title=".env" # Secret -AUTH_SECRET="" -AURA_AUTH_SECRET="" +AURA_AUTH_SECRET= # Salt -AUTH_SALT="" -AURA_AUTH_SALT="" +AURA_AUTH_SALT= ``` ### OAuth Variables -| Pattern | Description | -| ----------------------------------------------------------------------------------------------------- | ----------------------------------------- | -| `AURA_AUTH_{PROVIDER}_CLIENT_ID` \| `AUTH_{PROVIDER}_CLIENT_ID` \| `{PROVIDER}_CLIENT_ID` | Client ID obtained from the OAuth app | -| `AURA_AUTH_{PROVIDER}_CLIENT_SECRET` \| `AUTH_{PROVIDER}_CLIENT_SECRET` \| `{PROVIDER}_CLIENT_SECRET` | Client Secret obtained from the OAuth app | - - - -All of the environment variables that follow the previous patterns are loaded automatically by Aura Auth, so there is no need to -set the environment variables in the OAuth provider configuration. - - +| Pattern | Description | +| -------------------------- | ----------------------------------------- | +| `{PROVIDER}_CLIENT_ID` | Client ID obtained from the OAuth app | +| `{PROVIDER}_CLIENT_SECRET` | Client Secret obtained from the OAuth app | ```bash title=".env" # GitHub OAuth Credentials -GITHUB_CLIENT_ID="" -GITHUB_CLIENT_SECRET="" +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= # GitHub OAuth Credentials -AURA_GITHUB_CLIENT_ID="" -AURA_GITHUB_CLIENT_SECRET="" +AURA_GITHUB_CLIENT_ID= +AURA_GITHUB_CLIENT_SECRET= # GitHub OAuth Credentials -AURA_AUTH_GITHUB_CLIENT_ID="" -AURA_AUTH_GITHUB_CLIENT_SECRET="" +AURA_AUTH_GITHUB_CLIENT_ID= +AURA_AUTH_GITHUB_CLIENT_SECRET= ``` Some of the supported OAuth providers provided by Aura Auth include: @@ -86,22 +89,25 @@ Some of the supported OAuth providers provided by Aura Auth include: To see all the providers supported by Aura Auth, see [OAuth Providers](/docs/oauth). -```bash title=".env" -# GitHub OAuth Credentials -AURA_AUTH_GITHUB_CLIENT_ID="" -AURA_AUTH_GITHUB_CLIENT_SECRET="" +Never commit your `.env` file. Always use a secret manager in production. -# GitLab OAuth Credentials -AURA_AUTH_GITLAB_CLIENT_ID="" -AURA_AUTH_GITLAB_CLIENT_SECRET="" +### Runtime Variables -# Spotify OAuth Credentials -AURA_AUTH_SPOTIFY_CLIENT_ID="" -AURA_AUTH_SPOTIFY_CLIENT_SECRET="" +These variables control runtime behavior of the core auth module. -# Bitbucket OAuth Credentials -AURA_AUTH_BITBUCKET_CLIENT_ID="" -AURA_AUTH_BITBUCKET_CLIENT_SECRET="" -``` +| Key | Type | Description | +| ----------------- | --------- | ---------------------------------------------------------------------------- | +| `TRUSTED_ORIGINS` | `string` | Comma/semicolon/newline-separated origins used as trusted origin allowlist. | +| `DEBUG` | `boolean` | Enables debug logging when set to one of: `1`, `true`, `yes`, `on`, `debug`. | +| `LOG_LEVEL` | `string` | Logger level used by built-in logger (`debug`, `info`, `warn`, `error`). | -Never commit your `.env` file. Always use a secret manager in production. +```bash title=".env" +# Trusted origins (one or many) +AURA_AUTH_TRUSTED_ORIGINS="https://app.example.com,https://admin.example.com" + +# Debug mode +AURA_AUTH_DEBUG="1" + +# Built-in logger level +AURA_AUTH_LOG_LEVEL="info" +``` diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 6530bc24..4f67128e 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -10,9 +10,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added +- Added support for loading authentication configuration from environment variables, including `DEBUG`, `LOG_LEVEL`, `TRUSTED_ORIGINS`, and `TRUSTED_PROXY_HEADERS`. Also updated automatic environment variable loading patterns. [#108](https://github.com/aura-stack-ts/auth/pull/108) + - Added the `User-Agent` header to requests sent to the `/userInfo` endpoint, allowing the service to identify the calling client or application. [#105](https://github.com/aura-stack-ts/auth/pull/105) -- Introduced `timingSafeEqual` function for constant-time string comparison across runtimes. [#99](https://github.com/aura-stack-ts/auth/pull/99) +timingSafeEqual` function for constant-time string comparison across runtimes. [#99](https://github.com/aura-stack-ts/auth/pull/99) --- From 4e2c56f0352064a72400bcc6e0837b59e1781928 Mon Sep 17 00:00:00 2001 From: Hernan Alvarado Date: Sun, 1 Mar 2026 16:07:26 -0500 Subject: [PATCH 4/4] fix(core): fix trustedOrigins lecture --- docs/src/content/docs/configuration/env.mdx | 6 +++--- packages/core/CHANGELOG.md | 2 +- packages/core/src/index.ts | 3 ++- packages/core/src/logger.ts | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/src/content/docs/configuration/env.mdx b/docs/src/content/docs/configuration/env.mdx index 0ec72c64..c5691eac 100644 --- a/docs/src/content/docs/configuration/env.mdx +++ b/docs/src/content/docs/configuration/env.mdx @@ -29,9 +29,9 @@ For more details, read: - All the values of the environment variable is overridden the configuration option in the `createAuth` function. It includes the - `secret`, `trustedOrigins`, and `logger` options. So, if you set the environment variable for these options, it will override - the configuration provided in the `createAuth` function. + Environment variables override the corresponding configuration options in the `createAuth` function, including `secret`, + `trustedOrigins`, and `logger`. If you set an environment variable for these options, it will take precedence over the value + provided in `createAuth`. ### Secure Variables diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 4f67128e..b0590dd4 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -14,7 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - Added the `User-Agent` header to requests sent to the `/userInfo` endpoint, allowing the service to identify the calling client or application. [#105](https://github.com/aura-stack-ts/auth/pull/105) -timingSafeEqual` function for constant-time string comparison across runtimes. [#99](https://github.com/aura-stack-ts/auth/pull/99) +- Added `timingSafeEqual` function for constant-time string comparison across runtimes. [#99](https://github.com/aura-stack-ts/auth/pull/99) --- diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 478221f4..dd401b96 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -49,7 +49,8 @@ const createInternalConfig = (authConfig?: AuthConfig): RouterConfig => { secret: authConfig?.secret, basePath: authConfig?.basePath ?? "/auth", trustedProxyHeaders: useProxyHeaders, - trustedOrigins: getEnvArray("TRUSTED_ORIGINS") ?? authConfig?.trustedOrigins, + trustedOrigins: + getEnvArray("TRUSTED_ORIGINS").length > 0 ? getEnvArray("TRUSTED_ORIGINS") : authConfig?.trustedOrigins, logger, }, use: [ diff --git a/packages/core/src/logger.ts b/packages/core/src/logger.ts index 87c6627f..05e7290c 100644 --- a/packages/core/src/logger.ts +++ b/packages/core/src/logger.ts @@ -333,7 +333,7 @@ export const createLogger = (logger?: Logger): InternalLogger | undefined => { export const createProxyLogger = (config?: AuthConfig) => { const level = getEnv("LOG_LEVEL") const debug = getEnvBoolean("DEBUG") - if (debug || Boolean(config?.logger)) { + if (debug || config?.logger === true) { return createLogger({ level: (level as LogLevel) ?? "debug", log: createSyslogMessage,