Skip to content
Draft
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
202 changes: 202 additions & 0 deletions docs/src/content/docs/(core)/oauth/authentik.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
---
title: Authentik
description: Add Authentik authorization provider to Aura Auth for authentication and authorization
---

<Steps>

<Step>

## Authentik OAuth App

### Register the Application

The first step is to create and register an OAuth App on the Authentik Applications to obtain access to the user's resources.

1. Navigate to your Authentik profile and go to **Applications > Applications > New Provider**.
2. Click **New Application**.
3. Select **OAuth2/OIDC** as Provider Type
3. Fill in the "Application name" and "Homepage URL".
4. Set the **Authorization callback URL** to `http://localhost:3000/auth/callback/authentik`.
- _(Make sure to replace `localhost:3000` with your production domain when deploying)_
5. Click **Register application**.
6. Ensure you copy the **Client ID** and click **Generate a new client secret**.

</Step>

<Step>

## Installation

Install the package using a package manager like `npm`, `pnpm`, or `yarn`:

```npm
npm install @aura-stack/auth
```

</Step>

<Step>

## Environment setup

Now, you must configure the environment variables required by Aura Auth, including the Authentik credentials and the encryption secrets.

```bash title=".env" lineNumbers
# Aura Secrets
AURA_AUTH_SECRET="your-32-byte-secret"
AURA_AUTH_SALT="your-32-byte-salt"

# Authentik Credentials
AURA_AUTH_AUTHENTIK_CLIENT_ID="your_authentik_client_id"
AURA_AUTH_AUTHENTIK_CLIENT_SECRET="your_authentik_client_secret"
```

<Callout type="warn">
**CRITICAL SECURITY WARNING:** The `AURA_AUTH_SECRET` and `AURA_AUTH_SALT` variables are used to encrypt and sign user sessions.
These MUST be securely generated, highly randomized strings consisting of at least 32 bytes to ensure adequate entropy. Never
hardcode these values in your repository. Use a secure generator (like `openssl rand -base64 32`) to create them, and store them
exclusively in your secure environment variables manager.
</Callout>

</Step>

<Step>

## Configure the Auth Instance

Configure the `createAuth` instance inside an `auth.ts` file located at the root of your project. Ensure you explicitly export the `handlers`, `api`, and `jose` objects.

```ts title="auth.ts" lineNumbers
import { createAuth } from "@aura-stack/auth"

export const auth = createAuth({
oauth: ["authentik"],
})

// Extract the required utilities
export const { handlers, api, jose } = auth
```

<Callout type="info">
The `handlers` object contains mapping utilities for standard HTTP methods (`GET`, `POST`, `PATCH`) as well as a unified `ALL`
handler. This allows you to easily mount the authentication routes across any framework (Next.js, Elysia, Express, etc.).
</Callout>

</Step>

<Step>

## Customizing the OAuth Provider

If you need to define custom scopes, change the response type, or map profile data differently, you can use the provider's factory function instead of a simple string identifier.

```ts title="auth.ts" lineNumbers
import { createAuth } from "@aura-stack/auth"
import { authentik } from "@aura-stack/auth/oauth/authentik"

export const auth = createAuth({
oauth: [
authentik({
authorize: {
params: {
// Override default scopes
scope: "read:user user:email",
},
},
}),
Comment on lines +98 to +107

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the referenced docs file and provider implementation.
git ls-files 'docs/src/content/docs/(core)/oauth/authentik.mdx' 'packages/core/src/oauth/authentik.ts' 'packages/core/src/oauth/*.ts' 'packages/core/src/oauth/**/*.ts'

echo '--- authentik docs excerpt ---'
sed -n '80,130p' docs/src/content/docs/\(core\)/oauth/authentik.mdx

echo '--- authentik provider file outline ---'
ast-grep outline packages/core/src/oauth/authentik.ts --view expanded || true

echo '--- provider source excerpt ---'
sed -n '1,220p' packages/core/src/oauth/authentik.ts

echo '--- search for OpenIDProvider definition and authorize/scope fields ---'
rg -n "interface OpenIDProvider|type OpenIDProvider|authorize:|scope\\?" packages/core/src/oauth packages/core/src -g '*.ts'

Repository: aura-stack-ts/auth

Length of output: 6054


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the surrounding docs/example more precisely if needed.
wc -l docs/src/content/docs/\(core\)/oauth/authentik.mdx packages/core/src/oauth/authentik.ts || true

Repository: aura-stack-ts/auth

Length of output: 263


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '200,250p' packages/core/src/@types/oidc.ts
echo '---'
sed -n '1,140p' packages/core/src/@types/oauth.ts
echo '---'
sed -n '1,120p' packages/core/src/schemas.ts
echo '---'
sed -n '60,110p' packages/core/src/createAuth.ts
echo '---'
sed -n '1,120p' packages/core/src/actions/oidc/authorization-url.ts

Repository: aura-stack-ts/auth

Length of output: 12159


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '200,250p' packages/core/src/@types/oidc.ts
echo '---'
sed -n '1,140p' packages/core/src/@types/oauth.ts
echo '---'
sed -n '1,120p' packages/core/src/schemas.ts
echo '---'
sed -n '60,110p' packages/core/src/createAuth.ts
echo '---'
sed -n '1,120p' packages/core/src/actions/oidc/authorization-url.ts

Repository: aura-stack-ts/auth

Length of output: 12159


Fix the customization example to use the top-level scope option.
authentik() accepts scope directly on the provider config, not authorize.params, so this example does not match the API. Use an OIDC scope string like openid profile email instead.

Suggested fix
 export const auth = createAuth({
   oauth: [
     authentik({
-      authorize: {
-        params: {
-          // Override default scopes
-          scope: "read:user user:email",
-        },
-      },
+      scope: "openid profile email",
     }),
   ],
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const auth = createAuth({
oauth: [
authentik({
authorize: {
params: {
// Override default scopes
scope: "read:user user:email",
},
},
}),
export const auth = createAuth({
oauth: [
authentik({
scope: "openid profile email",
}),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/src/content/docs/`(core)/oauth/authentik.mdx around lines 98 - 107, The
customization example for authentik is using the wrong API shape: update the
`authentik()` provider config in the `createAuth` example to pass `scope` at the
top level instead of nesting it under `authorize.params`. Use an OIDC-style
scope string such as `openid profile email`, and keep the example aligned with
the `authentik` provider options shown in this docs snippet.

],
})

export const { handlers, api, jose } = auth
```

</Step>

<Step>

## Sign In to Authentik (Client & Server)

There are multiple ways to trigger the sign-in flow depending on your ecosystem.

### Sign-in Path (Direct Navigation)

The common route to trigger the auth flow natively without needing a client library is simply navigating the browser to:
`http://localhost:3000/auth/signIn/authentik`

---

### Client-Side (React, Vue, etc.)

You can utilize the `createAuthClient` utility to programmatically trigger sign-ins. You can also define a `redirectTo` destination.

<Callout type="warn">
**Constraint Rule**: The `baseURL` passed into `createAuthClient` MUST exactly match the root domain and path where the HTTP
`handlers` expose their endpoints on the server.
</Callout>

```ts title="components/Login.tsx" lineNumbers
import { createAuthClient } from "@aura-stack/auth/client"

export const authClient = createAuthClient({
baseURL: "http://localhost:3000/auth",
})

const triggerSignIn = async () => {
await authClient.signIn("authentik", {
redirectTo: "/dashboard",
})
}
```

---

### Server-Side (Next.js Actions, Remix Loaders, etc.)

For environments supporting server-side actions, use the programmatic `api.signIn` method securely.

```ts title="actions.ts" lineNumbers
import { api } from "./auth"

export const serverSignIn = async () => {
const response = await api.signIn("authentik", {
redirectTo: "http://localhost:3000/dashboard",
})

// Example returning redirect location
return response.headers.get("Location")
}
```

---

### Session Retrieval

After a user successfully signs in, you can retrieve their session data securely.

**Client-Side:**

```ts
const session = await authClient.getSession()
console.log(session?.user) // The authenticated Authentik user profile
```

**Server-Side:**

```ts
// Note: You must pass the native Web Request object or Headers!
const session = await api.getSession(request)
console.log(session?.user) // Safely retrieved backend session
```

</Step>

</Steps>

---

## Resources

- [RFC - The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749)
- [Authentik - OAuth 2.0 Provider](https://docs.goauthentik.io/add-secure-apps/providers/oauth2/)
- [Authentik - Create an OAuth2 Provider](https://docs.goauthentik.io/add-secure-apps/providers/oauth2/create-oauth2-provider/)
45 changes: 45 additions & 0 deletions packages/core/src/oauth/authentik.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { OpenIDProvider, User } from "@/@types/index.ts"

export interface AuthentikProfile {
iss: string
sub: string
aud: string
exp: number
iat: number
auth_time: number
acr: string
c_hash: string
nonce: string
at_hash: string
email: string
email_verified: boolean
name: string
given_name: string
family_name: string
preferred_username: string
nickname: string
}

/**
* Authentik OpenID Connect Provider
*
* @see [Authentik - OAuth 2.0 Provider](https://docs.goauthentik.io/add-secure-apps/providers/oauth2/)
* @see [Authentik - Create an OAuth2 Provider](https://docs.goauthentik.io/add-secure-apps/providers/oauth2/create-oauth2-provider/)
*/
export const authentik = <DefaultUser extends User = User>(
options?: Partial<OpenIDProvider<AuthentikProfile, DefaultUser>>
): OpenIDProvider<AuthentikProfile, DefaultUser> => {
return {
id: "authentik",
name: "Authentik",
issuer: "https://authentik.company/application/o/:application_slug/.well-known/openid-configuration,",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '## Files\n'
git ls-files packages/core/src/oauth/authentik.ts packages/core/src/@types/oidc.ts packages/core/test/presets.ts

printf '\n## Outline: authentik.ts\n'
ast-grep outline packages/core/src/oauth/authentik.ts --view expanded || true

printf '\n## Outline: oidc.ts\n'
ast-grep outline packages/core/src/@types/oidc.ts --view expanded || true

printf '\n## Outline: presets.ts\n'
ast-grep outline packages/core/test/presets.ts --view expanded || true

printf '\n## Read relevant slices\n'
for f in packages/core/src/oauth/authentik.ts packages/core/src/@types/oidc.ts packages/core/test/presets.ts; do
  echo "===== $f ====="
  nl -ba "$f" | sed -n '1,220p'
done

Repository: aura-stack-ts/auth

Length of output: 3128


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '## Search for authentik usage and OIDC contract\n'
rg -n "authentik|issuer:|well-known/openid-configuration|email_verified|nickname|OpenIDProvider<" packages/core -S

printf '\n## Search for preset expectations around profile fields\n'
rg -n "nickname|email_verified|image|email" packages/core/test -S

Repository: aura-stack-ts/auth

Length of output: 26883


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '## Files\n'
git ls-files packages/core/src/oauth/authentik.ts packages/core/src/@types/oidc.ts packages/core/test/presets.ts

printf '\n## Outline: authentik.ts\n'
ast-grep outline packages/core/src/oauth/authentik.ts --view expanded || true

printf '\n## Outline: oidc.ts\n'
ast-grep outline packages/core/src/@types/oidc.ts --view expanded || true

printf '\n## Outline: presets.ts\n'
ast-grep outline packages/core/test/presets.ts --view expanded || true

printf '\n## Read relevant slices\n'
for f in packages/core/src/oauth/authentik.ts packages/core/src/@types/oidc.ts packages/core/test/presets.ts; do
  echo "===== $f ====="
  nl -ba "$f" | sed -n '1,220p'
done

Repository: aura-stack-ts/auth

Length of output: 3128


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '## Search for authentik usage and OIDC contract\n'
rg -n "authentik|issuer:|well-known/openid-configuration|email_verified|nickname|OpenIDProvider<" packages/core -S

printf '\n## Search for preset expectations around profile fields\n'
rg -n "nickname|email_verified|image|email" packages/core/test -S

Repository: aura-stack-ts/auth

Length of output: 26883


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in packages/core/src/oauth/authentik.ts packages/core/src/@types/oidc.ts packages/core/test/presets.ts; do
  echo "===== $f ====="
  awk '{printf "%4d  %s\n", NR, $0}' "$f" | sed -n '1,220p'
done

Repository: aura-stack-ts/auth

Length of output: 21690


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "===== authentik provider ====="
awk '{printf "%4d  %s\n", NR, $0}' packages/core/src/oauth/authentik.ts | sed -n '1,120p'

echo
echo "===== OIDC provider contract ====="
awk '{printf "%4d  %s\n", NR, $0}' packages/core/src/@types/oidc.ts | sed -n '205,230p'

echo
echo "===== preset mapping ====="
awk '{printf "%4d  %s\n", NR, $0}' packages/core/test/presets.ts | sed -n '1,90p'

Repository: aura-stack-ts/auth

Length of output: 5626


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in packages/core/src/oauth/authentik.ts packages/core/src/@types/oidc.ts packages/core/test/presets.ts; do
  echo "===== $f ====="
  awk '{printf "%4d  %s\n", NR, $0}' "$f" | sed -n '1,220p'
done

Repository: aura-stack-ts/auth

Length of output: 21690


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "===== authentik provider ====="
awk '{printf "%4d  %s\n", NR, $0}' packages/core/src/oauth/authentik.ts | sed -n '1,120p'

echo
echo "===== OIDC provider contract ====="
awk '{printf "%4d  %s\n", NR, $0}' packages/core/src/@types/oidc.ts | sed -n '205,230p'

echo
echo "===== preset mapping ====="
awk '{printf "%4d  %s\n", NR, $0}' packages/core/test/presets.ts | sed -n '1,90p'

Repository: aura-stack-ts/auth

Length of output: 5626


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "authentik\s*\(" packages/core -S

Repository: aura-stack-ts/auth

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

awk '{printf "%4d  %s\n", NR, $0}' packages/core/src/actions/oidc/discovery.ts | sed -n '1,120p'

Repository: aura-stack-ts/auth

Length of output: 1987


🏁 Script executed:

#!/bin/bash
set -euo pipefail

awk '{printf "%4d  %s\n", NR, $0}' packages/core/src/actions/oidc/resolve-provider.ts | sed -n '1,120p'

Repository: aura-stack-ts/auth

Length of output: 2982


Make issuer the base Authentik URL
OpenIDProvider.issuer is appended with /.well-known/openid-configuration, so the current value (.../.well-known/openid-configuration,) produces a broken discovery URL. It also needs to be configurable per deployment instead of hardcoding the host and application slug.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/oauth/authentik.ts` at line 35, The Authentik OpenID issuer
is currently hardcoded to a full discovery URL with a trailing comma, which
breaks URL construction in OpenIDProvider.issuer; update the authentik config in
authentiK.ts so issuer is only the base Authentik URL, and make it
deployment-configurable instead of embedding the host and application slug
directly. Use the existing Authentik/OpenID provider setup to locate the issuer
assignment and ensure the discovery endpoint is still derived by the provider.

profile: (profile) =>
({
sub: profile.sub,
name: profile.name,
email: profile.email,
image: null,
}) as DefaultUser,
Comment on lines +36 to +42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and nearby type definitions.
git ls-files packages/core/src/oauth/authentik.ts packages/core/src/** | sed -n '1,120p'
printf '\n--- outline authentik.ts ---\n'
ast-grep outline packages/core/src/oauth/authentik.ts --view expanded || true
printf '\n--- search for DefaultUser and AuthentikProfile ---\n'
rg -n "type DefaultUser|interface DefaultUser|DefaultUser|AuthentikProfile|email_verified|nickname|preferred_username" packages/core/src -S

Repository: aura-stack-ts/auth

Length of output: 15030


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant file and the type definitions it depends on.
wc -l packages/core/src/oauth/authentik.ts || true
printf '\n--- authentik.ts ---\n'
cat -n packages/core/src/oauth/authentik.ts

printf '\n--- oauth type definitions search ---\n'
rg -n "type DefaultUser|interface DefaultUser|export .*DefaultUser|AuthentikProfile|preferred_username|email_verified|nickname" packages/core/src -S

Repository: aura-stack-ts/auth

Length of output: 5458


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the shared OIDC/OAuth schema around DefaultUser if found.
files=$(rg -l "DefaultUser|AuthentikProfile|preferred_username|email_verified|nickname" packages/core/src -S || true)
printf '%s\n' "$files"
for f in $files; do
  echo "=== $f ==="
  wc -l "$f"
done

Repository: aura-stack-ts/auth

Length of output: 2052


🏁 Script executed:

#!/bin/bash
set -euo pipefail

wc -l packages/core/src/oauth/authentik.ts
printf '\n--- authentik.ts ---\n'
sed -n '1,120p' packages/core/src/oauth/authentik.ts

printf '\n--- relevant symbols ---\n'
rg -n "type DefaultUser|interface DefaultUser|export .*DefaultUser|AuthentikProfile|preferred_username|email_verified|nickname" packages/core/src -S

Repository: aura-stack-ts/auth

Length of output: 5130


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact file and the shared user/profile type definitions.
sed -n '1,120p' packages/core/src/oauth/authentik.ts

printf '\n--- search for DefaultUser / profile schema ---\n'
rg -n "AuthentikProfile|email_verified|nickname|preferred_username|DefaultUser" packages/core/src -S

Repository: aura-stack-ts/auth

Length of output: 12727


🌐 Web query:

"AuthentikProfile email_verified nickname DefaultUser oauth auth package"

💡 Result:

In authentik, claims such as email_verified, nickname, and other profile information are managed via Scope Mappings in an OAuth2/OIDC Provider [1]. These claims are not static; they are dynamically generated using Python expressions defined in the provider's property mappings [1][2]. Key details regarding your query include: 1. email_verified Claim: Historically, authentik defaulted email_verified to True [3][4]. However, as of release 2025.10, this defaults to False because authentik does not have an internal mechanism to verify email addresses by default [3][1]. If your application requires this to be True, you can create a custom scope mapping or modify an existing one to return a hardcoded True value, or better, map it to a user attribute (e.g., request.user.attributes.get("email_verified", False)) that you manage elsewhere [3]. 2. nickname Claim: The nickname claim is typically included in the default OAuth Mapping: OpenID 'profile' scope [2]. The standard expression for this mapping is: return { "name": request.user.name, "given_name": request.user.name, "preferred_username": request.user.username, "nickname": request.user.username, "groups": [group.name for group in request.user.groups.all], } 3. DefaultUser and Package Context: There is no "DefaultUser" object in the OAuth context; rather, the mapping has access to a request object, specifically request.user, which represents the user currently authenticating [3][1][2]. This allows you to pull attributes, usernames, or groups directly from the user's profile [5][2][6]. 4. Implementation: To customize these claims, you must: * Navigate to Customization > Property Mappings in the authentik admin interface [3]. * Create or edit an OAuth2 Scope Mapping [1]. * Use the Python expression syntax (as shown above) to return a dictionary of claims [1]. * Assign this mapping to your specific OAuth2 Provider under the 'Property mappings' field [1][7]. * Ensure the client application requests the corresponding scope (e.g., 'profile' or 'email') when initiating the OAuth flow [3][7]. If you are using default setups, these mappings are managed by blueprints [2][8]. If you find your scopes are missing or behaving unexpectedly, verify that the default system blueprints are enabled in your instance [2][8].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- schemas.ts relevant user shape ---\n'
sed -n '240,280p' packages/core/src/schemas.ts

printf '\n--- any runtime usage of email_verified / nickname / preferred_username ---\n'
rg -n "\b(email_verified|nickname|preferred_username)\b" packages/core/src -S

Repository: aura-stack-ts/auth

Length of output: 247


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- schemas.ts relevant user shape ---\n'
sed -n '240,280p' packages/core/src/schemas.ts

printf '\n--- any runtime usage of email_verified / nickname / preferred_username ---\n'
rg -n "\b(email_verified|nickname|preferred_username)\b" packages/core/src -S

Repository: aura-stack-ts/auth

Length of output: 247


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- schemas.ts relevant user shape ---\n'
sed -n '240,280p' packages/core/src/schemas.ts

printf '\n--- any runtime usage of email_verified / nickname / preferred_username ---\n'
rg -n "\b(email_verified|nickname|preferred_username)\b" packages/core/src -S

Repository: aura-stack-ts/auth

Length of output: 247


Forward Authentik’s optional claims into the mapped user. email_verified, nickname, and preferred_username are already on AuthentikProfile and supported by the shared user schema, so mapping them through keeps Authentik payloads consistent for consumers that rely on verified-email or display-name fallbacks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/oauth/authentik.ts` around lines 36 - 42, The Authentik
profile mapping in authentik.ts is dropping optional claims that are already
available on AuthentikProfile and supported by DefaultUser. Update the
profile-to-user mapping in the Authentik provider’s profile callback to pass
through email_verified, nickname, and preferred_username alongside the existing
sub, name, email, and image fields so downstream consumers receive the full
Authentik payload.

...options,
}
}
Loading