Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions docs/src/content/docs/api-reference/core.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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"
```

<Callout type="warn">Never commit `.env` files to version control. Add them to `.gitignore`.</Callout>
Expand Down
96 changes: 51 additions & 45 deletions docs/src/content/docs/configuration/env.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<Callout>

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)

</Callout>

<Callout type="warn">
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`.
</Callout>

### 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 |

<Callout>
The `AURA_AUTH_SECRET` environment variable is overridden by `secret` configuration option in the `createAuth` function.
</Callout>
| 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

Expand All @@ -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 |

<Callout>

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.

</Callout>
| 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:
Expand All @@ -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=""
<Callout type="warn">Never commit your `.env` file. Always use a secret manager in production.</Callout>

# 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`). |

<Callout type="warn">Never commit your `.env` file. Always use a secret manager in production.</Callout>
```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"
```
4 changes: 3 additions & 1 deletion packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- Added `timingSafeEqual` function for constant-time string comparison across runtimes. [#99](https://github.com/aura-stack-ts/auth/pull/99)

---

Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/@types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,16 @@ 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
* @experimental
*/
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
Expand Down
22 changes: 22 additions & 0 deletions packages/core/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,25 @@ export const env = new Proxy({} as Record<string, string | undefined>, {
}
},
})

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: string[] = []) => {
const value = getEnv(key)
if (!value) return defaultValue
return value
.split(/[,;\n]+/)
.map((v) => v.trim())
.filter(Boolean)
}
64 changes: 16 additions & 48 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -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 { 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"
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,
Expand All @@ -27,63 +28,30 @@ 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<LogLevel, string[]> = {
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<T extends keyof typeof logMessages>(key: T, overrides?: Partial<SyslogOptions>) {
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 trustedProxyHeadersEnv = getEnv("TRUSTED_PROXY_HEADERS")
const useProxyHeaders =
trustedProxyHeadersEnv === undefined ? (authConfig?.trustedProxyHeaders ?? false) : getEnvBoolean("TRUSTED_PROXY_HEADERS")
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").length > 0 ? getEnvArray("TRUSTED_ORIGINS") : authConfig?.trustedOrigins,
logger,
},
use: [
(ctx) => {
Expand All @@ -92,7 +60,7 @@ const createInternalConfig = (authConfig?: AuthConfig): RouterConfig => {
useSecure,
authConfig?.cookies?.prefix,
authConfig?.cookies?.overrides ?? {},
internalLogger
logger
)
ctx.context.cookies = cookies
return ctx
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/jose.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { env } from "@/env.ts"
import { getEnv } from "@/env.ts"
import {
createJWT,
createJWS,
Expand All @@ -21,15 +21,15 @@ 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",
"AURA_AUTH_SECRET environment variable is not set and no secret was provided."
)
}

const salt = env.AURA_AUTH_SALT ?? env.AUTH_SALT
const salt = getEnv("SALT")
if (!salt) {
throw new AuthInternalError(
"JOSE_INITIALIZATION_FAILED",
Expand Down
Loading