Skip to content

feat: configurable SAML attributes and audience via environment variables#1200

Open
chipach wants to merge 3 commits into
ory:mainfrom
chipach:feat/configurable-saml-attributes
Open

feat: configurable SAML attributes and audience via environment variables#1200
chipach wants to merge 3 commits into
ory:mainfrom
chipach:feat/configurable-saml-attributes

Conversation

@chipach
Copy link
Copy Markdown

@chipach chipach commented May 5, 2026

Closes #156

Summary

  • SAML_ATTRIBUTE_<name>=<value> env vars inject extra attributes into the SAML assertion. Values support {id}, {email}, {firstName}, {lastName} placeholders.
  • SAML_AUDIENCE=<url> sets the default audience pre-filled on the login form (default: https://saml.boxyhq.com).
  • The login page shows an editable Attributes section seeded from env defaults — values can be changed, rows removed with ✕, and new attributes added with the + row before submitting.

Example

SAML_ATTRIBUTE_role=admin
SAML_ATTRIBUTE_department=engineering
SAML_ATTRIBUTE_uid={id}
SAML_AUDIENCE=https://my-sp.example.com

Notes

  • Placeholder resolution (including {id}) happens server-side, so all placeholders work correctly even after UI edits.
  • next.config.js gains outputFileTracingRoot to silence a spurious workspace-root warning from Next.js 16 when a lockfile exists in a parent directory.
  • tsconfig.json moduleResolution updated from node to bundler — Next.js 16 flags this as a mandatory change and rewrites the file automatically on every build.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • SAML authentication now supports audience configuration
    • Added support for extra SAML attributes with dynamic templating (user ID, email, first/last name placeholders)
    • SAML attributes can be configured via environment variables or the login interface
  • Chores

    • Updated deployment configuration for improved container builds

@vercel
Copy link
Copy Markdown

vercel Bot commented May 5, 2026

@chipach is attempting to deploy a commit to the ory Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 47cbeb1b-2b35-4d2f-9aa6-37e9dfdaf238

📥 Commits

Reviewing files that changed from the base of the PR and between c37e4fe and 33e5792.

📒 Files selected for processing (6)
  • .env.example
  • lib/env.ts
  • next.config.js
  • pages/api/saml/auth.ts
  • pages/namespace/[namespace]/saml/login.tsx
  • pages/saml/login.tsx
✅ Files skipped from review due to trivial changes (2)
  • .env.example
  • next.config.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/env.ts
  • pages/saml/login.tsx
  • pages/api/saml/auth.ts

📝 Walkthrough

Walkthrough

This PR adds support for custom SAML attributes in authentication responses. Configuration for audience and extra attributes is parsed from environment variables, the authentication endpoint dynamically builds claims from provided or configured attributes, and the login form includes a UI for managing attribute mappings with template placeholder support.

Changes

SAML Custom Attributes Support

Layer / File(s) Summary
Configuration Parsing
lib/env.ts, .env.example
Added audience from SAML_AUDIENCE env var and extraAttributes by scanning SAML_ATTRIBUTE_* env vars; documentation in .env.example defines the pattern and placeholder variables ({id}, {email}, {firstName}, {lastName}).
Backend Claim Processing
pages/api/saml/auth.ts
Added resolveTemplate() helper to substitute placeholders in attribute values; POST handler now accepts attributes array from request body and builds extraClaims from either provided attributes or config.extraAttributes, merging claims into SAML response.
Server-Side Props
pages/saml/login.tsx
New getServerSideProps exports config.extraAttributes as defaultAttributes and config.audience as defaultAudience to hydrate the page.
Frontend Form & State
pages/saml/login.tsx
Component now accepts defaultAttributes and defaultAudience props; manages editable attributes array state with add/remove/edit handlers; form submission includes resolved attributes in POST body. UI adds a dedicated "Attributes" editor section with inputClass for consistent styling.
Routing Wiring
pages/namespace/[namespace]/saml/login.tsx
Updated to re-export both default and getServerSideProps from parent route, enabling namespace-scoped SAML login to access server props.
Build Configuration
next.config.js
Added outputFileTracingRoot: __dirname alongside existing output: 'standalone' for proper dependency tracing in standalone builds.

Sequence Diagram

sequenceDiagram
    actor User
    participant Browser
    participant LoginPage as Login Page<br/>(pages/saml/login.tsx)
    participant AuthAPI as Auth API<br/>(pages/api/saml/auth.ts)
    participant Config as Config<br/>(lib/env.ts)
    participant IdP as SAML IdP

    Browser->>LoginPage: Load /saml/login
    LoginPage->>Config: getServerSideProps reads<br/>config.extraAttributes,<br/>config.audience
    Config-->>LoginPage: Return defaultAttributes,<br/>defaultAudience
    LoginPage-->>Browser: Render form with<br/>default attributes
    
    User->>Browser: Edit attributes,<br/>enter credentials
    Browser->>AuthAPI: POST /api/saml/auth<br/>{audience, acsUrl,<br/>attributes, ...}
    
    AuthAPI->>Config: Read config.extraAttributes<br/>(fallback if needed)
    AuthAPI->>AuthAPI: resolveTemplate()<br/>for each attribute value
    AuthAPI->>AuthAPI: Build extraClaims<br/>from attributes
    AuthAPI->>AuthAPI: Merge into claims:<br/>{...user, ...extraClaims}
    
    AuthAPI->>AuthAPI: Sign SAML response<br/>with custom claims
    AuthAPI-->>Browser: Return HTML form<br/>with SAML assertion
    
    Browser->>IdP: Auto-POST SAML<br/>response with<br/>custom attributes
    IdP-->>Browser: Validate & process<br/>custom claims
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes to next.config.js (outputFileTracingRoot) and tsconfig.json moduleResolution are miscellaneous build/config improvements not directly related to issue #156's custom attributes feature. Consider moving build configuration changes (next.config.js, tsconfig.json) to a separate PR to maintain focused scope on custom SAML attributes functionality.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: adding configurable SAML attributes and audience via environment variables, matching the primary changes across all modified files.
Linked Issues check ✅ Passed The PR fully implements issue #156's requirement for custom SAML attributes via environment variables (SAML_ATTRIBUTE_*) with template placeholder support, directly addressing the request for givenName, lastName, email attributes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…bles

Closes ory#156

Adds support for injecting custom SAML attributes and configuring the
default audience via environment variables, with an editable UI on the
login page.

## Environment variables

- `SAML_ATTRIBUTE_<name>=<value>` — adds `<name>` as an extra attribute
  in the SAML assertion. Values support placeholders: `{id}`, `{email}`,
  `{firstName}`, `{lastName}`.
- `SAML_AUDIENCE=<url>` — sets the default audience (SP entity ID)
  pre-filled on the login form (default: `https://saml.boxyhq.com`).

## Login page

The Attributes section on the login form is seeded from env defaults but
is fully editable before submitting:
- Change any attribute name or value
- Remove an attribute with ✕
- Add new attributes with the + row (Enter also commits a new row)

Attribute values (including `{id}`) are resolved server-side so all
placeholders work correctly regardless of edits made in the UI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@chipach chipach force-pushed the feat/configurable-saml-attributes branch from c37e4fe to 7013cbb Compare May 5, 2026 03:01
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
next.config.js (1)

7-7: 💤 Low value

path.join(__dirname) with a single argument is a no-op — use __dirname directly.

path.join with one argument simply normalises and returns that path unchanged, so path.join(__dirname) is identical to __dirname. The intent is clear, but the wrapping call adds noise.

✨ Proposed simplification
-  outputFileTracingRoot: path.join(__dirname),
+  outputFileTracingRoot: __dirname,

If path is no longer needed after this change, you can also drop the require:

-const path = require('path');
-
🤖 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 `@next.config.js` at line 7, Replace the no-op path.join call by using
__dirname directly for outputFileTracingRoot: change usages of
path.join(__dirname) to __dirname (i.e., update the outputFileTracingRoot
assignment referencing outputFileTracingRoot), and if the path module is no
longer used elsewhere in the file, remove the unused require/import of path to
keep the file clean.
pages/saml/login.tsx (1)

6-6: ⚡ Quick win

Move server-only config import inside getServerSideProps to prevent it being bundled client-side.

config is used exclusively in getServerSideProps (line 276), yet the module-level import config from 'lib/env' causes lib/env.ts — which calls fetchPrivateKey() / fetchPublicKey() at module initialization — to be included in the client bundle. Next.js replaces non-NEXT_PUBLIC_* env vars with undefined so key material won't literally appear in the bundle, but shipping the module unnecessarily increases bundle size and blurs the server/client boundary.

♻️ Proposed refactor — scope the import to the server function
 import Head from 'next/head';
 import { useRouter } from 'next/router';
 import type { GetServerSideProps } from 'next';
 import type { FormEvent } from 'react';
 import { useEffect, useRef, useState } from 'react';
-import config from 'lib/env';
 
 // ... component unchanged ...
 
 export const getServerSideProps: GetServerSideProps<Props> = async () => {
+  const { default: config } = await import('lib/env');
   const defaultAttributes = Object.entries(config.extraAttributes).map(([name, value]) => ({
     name,
     value,
   }));
   return { props: { defaultAttributes, defaultAudience: config.audience } };
 };

Alternatively, add server-only as a dependency and import it at the top of lib/env.ts to have Next.js enforce server-side-only usage at build time.

🤖 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 `@pages/saml/login.tsx` at line 6, The module-level import "import config from
'lib/env'" pulls server-only key-fetching code into the client bundle; remove
that top-level import and instead require/import config inside the server-side
handler getServerSideProps (refer to getServerSideProps) so lib/env is only
loaded on the server, or alternatively add and import the "server-only" helper
at the top of lib/env.ts to enforce server-only usage; update any references in
the file to use the locally imported config within getServerSideProps.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@pages/api/saml/auth.ts`:
- Around line 8-14: The current resolveTemplate(template: string, user: User)
uses string replacements that allow special replacement patterns like $&/$' to
be interpreted, corrupting values when user fields (derived from email) contain
$. Change each .replace call to use the function form of
String.prototype.replace (e.g., replace(/\{firstName\}/g, () => user.firstName))
for all placeholders ({id},{email},{firstName},{lastName}) so the literal user
values are inserted verbatim and special $ sequences are not processed.
- Around line 36-39: The loop over attributes can throw when an element is null
because it blindly destructures each item; before destructuring, guard that each
element is a non-null object (or pre-filter attributes to only objects) so you
only access name/value for valid items—update the loop that iterates over
attributes (the Array.isArray(attributes) block) to skip null/primitive entries
and only call resolveTemplate(value ?? '', user) and assign extraClaims[name]
when name exists on a valid object.

In `@pages/saml/login.tsx`:
- Around line 221-228: The name input (<input ... value={newAttr.name}
onChange={...} />) currently lacks an Enter key guard so pressing Enter bubbles
to the enclosing form and triggers handleSubmit; add the same key handler used
on the value input to the name input: intercept the Enter key, call
e.preventDefault(), and invoke handleAttrAdd() (or the same handler function
used by the value input) so the new attribute is added instead of submitting the
login form, updating the element that uses newAttr.name and setNewAttr.

---

Nitpick comments:
In `@next.config.js`:
- Line 7: Replace the no-op path.join call by using __dirname directly for
outputFileTracingRoot: change usages of path.join(__dirname) to __dirname (i.e.,
update the outputFileTracingRoot assignment referencing outputFileTracingRoot),
and if the path module is no longer used elsewhere in the file, remove the
unused require/import of path to keep the file clean.

In `@pages/saml/login.tsx`:
- Line 6: The module-level import "import config from 'lib/env'" pulls
server-only key-fetching code into the client bundle; remove that top-level
import and instead require/import config inside the server-side handler
getServerSideProps (refer to getServerSideProps) so lib/env is only loaded on
the server, or alternatively add and import the "server-only" helper at the top
of lib/env.ts to enforce server-only usage; update any references in the file to
use the locally imported config within getServerSideProps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e88fe2c9-5186-4978-a0c1-cf28c5e9be29

📥 Commits

Reviewing files that changed from the base of the PR and between 6792844 and c37e4fe.

📒 Files selected for processing (7)
  • .env.example
  • lib/env.ts
  • next.config.js
  • pages/api/saml/auth.ts
  • pages/namespace/[namespace]/saml/login.tsx
  • pages/saml/login.tsx
  • tsconfig.json

Comment thread pages/api/saml/auth.ts
Comment thread pages/api/saml/auth.ts
Comment thread pages/saml/login.tsx
Comment thread next.config.js Outdated
module.exports = {
reactStrictMode: true,
output: 'standalone',
outputFileTracingRoot: path.join(__dirname),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This was trying to look up a directory tree without this.

Comment thread pages/saml/login.tsx
}
};

const inputBase =
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Not completely required for this PR, but since classes were being added for the attributes, a little DRYing was in order.

- Use function replacements in resolveTemplate to prevent $ special
  sequences in user field values from corrupting attribute output
- Guard against null/non-object entries in the attributes array
- Add Enter key handler to attribute name input to prevent accidental
  form submission
- Simplify outputFileTracingRoot to use __dirname directly
- Move lib/env import inside getServerSideProps to keep it server-side only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for custom attributes

1 participant