<@U04UQ68083H> - create a login and registration f...
# ory-copilot
b
@chilly-king-10285 - create a login and registration flow with passwordless setup
b
A message from kapa.ai
I'll help you create login and registration flows with passwordless authentication in Ory. Let me walk you through the process. ## Creating a Passwordless Login Flow Ory supports passwordless authentication using one-time codes via email/SMS or passkeys (WebAuthn). Here's how to implement a login flow with passwordless authentication: ### 1. Login Flow with One-Time Code
Copy code
javascript import {     Configuration,     FrontendApi,     LoginFlow,     UiNode,     UiNodeInputAttributes,   } from "@ory/client"   import {     filterNodesByGroups,     isUiNodeInputAttributes,   } from "@ory/integrations/ui"   import { useEffect, useState } from "react"   import { useSearchParams } from "react-router-dom"      const frontend = new FrontendApi(     new Configuration({       basePath: "<http://localhost:4000>", // Use your local Ory Tunnel URL       baseOptions: {         withCredentials: true, // we need to include cookies       },     }),   )      export const Login = () => {     const [flow, setFlow] = useState<LoginFlow>()     const [searchParams] = useSearchParams()        useEffect(() => {       // check if the login flow is for two factor authentication       const aal2 = searchParams.get("aal2")       // we can redirect the user back to the page they were on before login       const returnTo = searchParams.get("return_to")          frontend         .createBrowserLoginFlow({           returnTo: returnTo || "/", // redirect to the root path after login           // if the user has a session, refresh it           refresh: true,           // if the aal2 query parameter is set, we get the two factor login flow UI nodes           aal: aal2 ? "aal2" : "aal1",         })         .then(({ data: flow }) => {           // set the flow data           setFlow(flow)         })         .catch((err) => {           // Couldn't create login flow           // handle the error         })     }, [])        const mapUINode = (node: UiNode, key: number) => {       // other node types are also supported       if (isUiNodeInputAttributes(node.attributes)) {         const attrs = node.attributes as UiNodeInputAttributes         const nodeType = attrs.type            switch (nodeType) {           case "button":           case "submit":             return (               <button                 type={attrs.type as "submit" | "reset" | "button" | undefined}                 name={attrs.name}                 value={attrs.value}               />             )           default:             return (               <input                 name={attrs.name}                 type={attrs.type}                 autoComplete={                   attrs.autocomplete || attrs.name === "identifier"                     ? "username"                     : ""                 }                 defaultValue={attrs.value}                 required={attrs.required}                 disabled={attrs.disabled}               />             )         }       }     }        return flow ? (       <form action={flow.ui.action} method={flow.ui.method}>         {filterNodesByGroups({           nodes: flow.ui.nodes,           // we will also map default fields here such as csrf_token           // modify this to include "code" for passwordless login           groups: ["default", "code"],         }).map((node, idx) => mapUINode(node, idx))}       </form>     ) : (       <div>Loading...</div>     )   }
### 2. For WebAuthn/Passkey Support For WebAuthn (passkeys), you need to include the WebAuthn script in your page:
Copy code
html <head>     <script src="/.well-known/ory/webauthn.js"></script>   </head>
Then modify your login flow to include the "webauthn" group:
Copy code
javascript // In your return statement return flow ? (     <form action={flow.ui.action} method={flow.ui.method}>       {filterNodesByGroups({         nodes: flow.ui.nodes,         // Include webauthn for passwordless authentication         groups: ["default", "webauthn"],       }).map((node, idx) => mapUINode(node, idx))}     </form>   ) : (     <div>Loading...</div>   )
## Creating a Passwordless Registration Flow For registration with passwordless options:
Copy code
javascript import { Configuration, FrontendApi } from "@ory/client"      const frontend = new FrontendApi(     new Configuration({       basePath: `https://${process.env.ORY_PROJECT_SLUG}.<http://projects.oryapis.com|projects.oryapis.com>`,     }),   )      export async function createRegistration() {     return await frontend.createBrowserRegistrationFlow()   }
Then in your registration component, filter for the passwordless methods:
Copy code
javascript return flow ? (     <form action={flow.ui.action} method={flow.ui.method}>       {filterNodesByGroups({         nodes: flow.ui.nodes,         // Include code or webauthn for passwordless registration         groups: ["default", "code", "webauthn"],       }).map((node, idx) => mapUINode(node, idx))}     </form>   ) : (     <div>Loading...</div>   )
## Configuration To enable passwordless authentication in your Ory project, you need to configure it in your Ory configuration:
Copy code
yaml selfservice:   methods:     code:       enabled: true       passwordless_enabled: true     webauthn:       enabled: true       config:         passwordless: true         rp:           display_name: Your App Name           id: <http://your-domain.com|your-domain.com>           origins:             - <https://your-domain.com>   registration:     after:       code:         hooks:           - hook: session  # This automatically logs users in after registration
For a unified sign-in/sign-up experience with passwordless authentication, you can create a custom UI that: 1. Initially shows only an email input field 2. Checks if the email exists 3. Triggers either login or registration flow based on email existence 4. Shows the OTP input field for code entry This approach allows for a seamless experience where users don't need to know if they already have an account or not. [Ory Identities](https://www.ory.sh/docs/identities) provides these passwordless authentication methods as part of its comprehensive identity management solution. Let me know if you need more specific details about any part of this implementation!
b
@chilly-king-10285 - can you provide a complete example for registering passwordless
b
A message from kapa.ai
I'll provide a complete example for implementing passwordless registration with Ory. This example will focus on the code-based (one-time code) passwordless registration flow. ## Complete Passwordless Registration Example Here's a complete React component example for implementing passwordless registration:
Copy code
javascript import {   Configuration,   FrontendApi,   RegistrationFlow,   UiNodeInputAttributes, } from "@ory/client" import {   filterNodesByGroups,   isUiNodeInputAttributes, } from "@ory/integrations/ui" import { useEffect, useState } from "react"  // Initialize the Ory Frontend API client const frontend = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>", // Use your Ory Tunnel URL or Ory Network project URL     baseOptions: {       withCredentials: true, // Important for cookies     },   }), )  function PasswordlessRegistration() {   const [flow, setFlow] = useState(null)   const [error, setError] = useState(null)    // Initialize the registration flow   useEffect(() => {     // Create a new registration flow     frontend.createBrowserRegistrationFlow()       .then(({ data }) => {         setFlow(data)       })       .catch((err) => {         setError(err.response?.data || err)         console.error(err)       })   }, [])    // Handle form submission   const handleSubmit = (e) => {     e.preventDefault()     const form = e.currentTarget     const formData = new FormData(form)          // Submit the form data to Ory     frontend.updateRegistrationFlow({       flow: flow.id,       updateRegistrationFlowBody: Object.fromEntries(formData)     })       .then(({ data }) => {         // If successful, the user will be redirected         // If additional steps are needed (like entering the code), update the flow         setFlow(data)       })       .catch((err) => {         setError(err.response?.data || err)         console.error(err)       })   }    if (error) {     return <div>An error occurred: {JSON.stringify(error)}</div>   }    if (!flow) {     return <div>Loading...</div>   }    return (     <div>       <h2>Register with Passwordless Authentication</h2>       <form action={flow.ui.action} method={flow.ui.method} onSubmit={handleSubmit}>         {/* Render all nodes from the "code" group and default group */}         {filterNodesByGroups({           nodes: flow.ui.nodes,           groups: ["default", "code"],         }).map((node, idx) => {           if (isUiNodeInputAttributes(node.attributes)) {             const attrs = node.attributes                          // Display messages for this node if any             const messages = node.messages.length > 0 && (               <div className="messages">                 {node.messages.map((message, i) => (                   <div key={i} className={`message ${message.type}`}>                     {message.text}                   </div>                 ))}               </div>             )                          // Display label if available             const label = node.meta?.label?.text && (               <label htmlFor={attrs.name}>{node.meta.label.text}</label>             )              // Render different input types             switch (attrs.type) {               case "submit":                 return (                   <div key={idx}>                     <button                        type="submit"                        name={attrs.name}                        value={attrs.value || ""}                       disabled={attrs.disabled}                     >                       {node.meta?.label?.text || "Submit"}                     </button>                     {messages}                   </div>                 )               case "hidden":                 return (                   <input                     key={idx}                     type={attrs.type}                     name={attrs.name}                     value={attrs.value || ""}                   />                 )               default:                 return (                   <div key={idx}>                     {label}                     <input                       id={attrs.name}                       name={attrs.name}                       type={attrs.type}                       value={attrs.value || ""}                       required={attrs.required}                       disabled={attrs.disabled}                       placeholder={node.meta?.label?.text}                     />                     {messages}                   </div>                 )             }           }           return null         })}       </form>     </div>   ) }  export default PasswordlessRegistration
## Configuration for Passwordless Registration To enable passwordless registration in your Ory project, you need to configure it properly. Here's the configuration you should add to your Ory configuration file:
Copy code
yaml selfservice:   methods:     code:       enabled: true       passwordless_enabled: true  # Enable passwordless login with code       config:         lifespan: 15m0s  # How long the code is valid   flows:     registration:       enabled: true       after:         code:           hooks:             - hook: session  # Automatically log users in after registration
## How the Passwordless Registration Flow Works 1. The user visits your registration page 2. Your application initializes a registration flow with Ory 3. The user enters their email address 4. The form is submitted to Ory 5. Ory sends a one-time code to the user's email 6. The user enters the code in your application 7. Upon successful verification, the user is registered and (if configured) automatically logged in ## Important Notes 1. For this to work, you need to have the "code" method enabled in your Ory configuration. 2. The
after.code.hooks
configuration with the
session
hook ensures users are automatically logged in after successful registration. 3. Two-step registration is now the default in Ory Kratos v1.2.0 and later [as mentioned in this discussion](https://github.com/ory/kratos/discussions/4025). If you want to create a unified experience where users don't need to know if they already have an account, you can implement a custom UI that checks if the email exists and then triggers either login or registration as appropriate, as [discussed here](https://github.com/ory/kratos/discussions/4294). For more information about self-service flows in Ory, you can refer to the [Self-service flows documentation](https://www.ory.sh/docs/kratos/self-service).
b
@chilly-king-10285 create boilerplate login and registration for passkey flow
b
A message from kapa.ai
I'll provide boilerplate code for implementing passkey-based login and registration flows with Ory. Passkeys offer a more secure passwordless authentication method using WebAuthn. ## Passkey Registration Flow Here's a complete React component for passkey registration:
Copy code
javascript import {   Configuration,   FrontendApi,   RegistrationFlow,   UiNodeInputAttributes,   UiNodeScriptAttributes, } from "@ory/client" import {   filterNodesByGroups,   isUiNodeInputAttributes, } from "@ory/integrations/ui" import { HTMLAttributeReferrerPolicy, useEffect, useState } from "react"  // Initialize the Ory Frontend API client const frontend = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>", // Use your Ory Tunnel URL or Ory Network project URL     baseOptions: {       withCredentials: true,     },   }), )  function PasskeyRegistration() {   const [flow, setFlow] = useState(null)   const [error, setError] = useState(null)    // Initialize the registration flow   useEffect(() => {     frontend.createBrowserRegistrationFlow()       .then(({ data }) => {         setFlow(data)       })       .catch((err) => {         setError(err.response?.data || err)         console.error(err)       })   }, [])    // Add the WebAuthn script to the DOM   useEffect(() => {     if (!flow) return      // Add the WebAuthn script     const scriptNodes = filterNodesByGroups({       nodes: flow.ui.nodes,       groups: "passkey",       attributes: "text/javascript",       withoutDefaultGroup: true,       withoutDefaultAttributes: true,     }).map((node) => {       const attr = node.attributes       const script = document.createElement("script")       script.src = attr.src       script.type = attr.type       script.async = attr.async       script.referrerPolicy = attr.referrerpolicy       script.crossOrigin = attr.crossorigin       script.integrity = attr.integrity       document.body.appendChild(script)       return script     })      // Check for onloadTrigger     const onloadNode = flow.ui.nodes.find(       (node) =>          isUiNodeInputAttributes(node.attributes) &&          node.attributes.onloadTrigger === "oryPasskeyRegistration"     )          if (onloadNode) {       // Execute the passkey registration trigger when available       window.setTimeout(() => {         if (window.__oryPasskeyRegistration) {           window.__oryPasskeyRegistration()         }       }, 500)     }      // Cleanup     return () => {       scriptNodes.forEach((script) => {         document.body.removeChild(script)       })     }   }, [flow])    if (error) {     return <div>An error occurred: {JSON.stringify(error)}</div>   }    if (!flow) {     return <div>Loading...</div>   }    return (     <div>       <h2>Register with Passkey</h2>       <form action={flow.ui.action} method={flow.ui.method}>         {/* Render all nodes from the "passkey" group and default group */}         {filterNodesByGroups({           nodes: flow.ui.nodes,           groups: ["default", "passkey"],           attributes: ["hidden", "submit", "button"],         }).map((node, idx) => {           if (isUiNodeInputAttributes(node.attributes)) {             const attrs = node.attributes                          // Display messages for this node if any             const messages = node.messages.length > 0 && (               <div className="messages">                 {node.messages.map((message, i) => (                   <div key={i} className={`message ${message.type}`}>                     {message.text}                   </div>                 ))}               </div>             )                          // Display label if available             const label = node.meta?.label?.text && (               <label htmlFor={attrs.name}>{node.meta.label.text}</label>             )              // Render different input types             switch (attrs.type) {               case "button":               case "submit":                 return (                   <div key={idx}>                     <button                        type={attrs.type}                       name={attrs.name}                        value={attrs.value || ""}                       disabled={attrs.disabled}                       onClick={attrs.onclick ? () => eval(attrs.onclick) : undefined}                     >                       {node.meta?.label?.text || "Submit"}                     </button>                     {messages}                   </div>                 )               default:                 return (                   <div key={idx}>                     {label}                     <input                       id={attrs.name}                       name={attrs.name}                       type={attrs.type}                       value={attrs.value || ""}                       required={attrs.required}                       disabled={attrs.disabled}                     />                     {messages}                   </div>                 )             }           }           return null         })}       </form>     </div>   ) }  export default PasskeyRegistration
## Passkey Login Flow Here's a complete React component for passkey login:
Copy code
javascript import {   Configuration,   FrontendApi,   LoginFlow,   UiNodeInputAttributes, } from "@ory/client" import {   filterNodesByGroups,   isUiNodeInputAttributes, } from "@ory/integrations/ui" import { useEffect, useState } from "react"  // Initialize the Ory Frontend API client const frontend = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>", // Use your Ory Tunnel URL or Ory Network project URL     baseOptions: {       withCredentials: true,     },   }), )  function PasskeyLogin() {   const [flow, setFlow] = useState(null)   const [error, setError] = useState(null)    // Initialize the login flow   useEffect(() => {     frontend.createBrowserLoginFlow({       refresh: true, // Refresh the session if it exists     })       .then(({ data }) => {         setFlow(data)       })       .catch((err) => {         setError(err.response?.data || err)         console.error(err)       })   }, [])    // Add the WebAuthn script to the DOM   useEffect(() => {     if (!flow) return      // Add the WebAuthn script     const scriptNodes = filterNodesByGroups({       nodes: flow.ui.nodes,       groups: "passkey",       attributes: "text/javascript",       withoutDefaultGroup: true,       withoutDefaultAttributes: true,     }).map((node) => {       const attr = node.attributes       const script = document.createElement("script")       script.src = attr.src       script.type = attr.type       script.async = attr.async       script.referrerPolicy = attr.referrerpolicy       script.crossOrigin = attr.crossorigin       script.integrity = attr.integrity       document.body.appendChild(script)       return script     })      // Check for onloadTrigger for auto-initialization     const onloadNode = flow.ui.nodes.find(       (node) =>          isUiNodeInputAttributes(node.attributes) &&          node.attributes.onloadTrigger === "oryPasskeyLoginAutocompleteInit"     )          if (onloadNode) {       // Execute the passkey login autocomplete trigger when available       window.setTimeout(() => {         if (window.__oryPasskeyLoginAutocompleteInit) {           window.__oryPasskeyLoginAutocompleteInit()         }       }, 500)     }      // Cleanup     return () => {       scriptNodes.forEach((script) => {         document.body.removeChild(script)       })     }   }, [flow])    if (error) {     return <div>An error occurred: {JSON.stringify(error)}</div>   }    if (!flow) {     return <div>Loading...</div>   }    return (     <div>       <h2>Login with Passkey</h2>       <form action={flow.ui.action} method={flow.ui.method}>         {/* Render all nodes from the "passkey" group and default group */}         {filterNodesByGroups({           nodes: flow.ui.nodes,           groups: ["default", "passkey"],         }).map((node, idx) => {           if (isUiNodeInputAttributes(node.attributes)) {             const attrs = node.attributes                          // Display messages for this node if any             const messages = node.messages.length > 0 && (               <div className="messages">                 {node.messages.map((message, i) => (                   <div key={i} className={`message ${message.type}`}>                     {message.text}                   </div>                 ))}               </div>             )                          // Display label if available             const label = node.meta?.label?.text && (               <label htmlFor={attrs.name}>{node.meta.label.text}</label>             )              // Render different input types             switch (attrs.type) {               case "button":               case "submit":                 return (                   <div key={idx}>                     <button                        type={attrs.type}                       name={attrs.name}                        value={attrs.value || ""}                       disabled={attrs.disabled}                       onClick={attrs.onclick ? () => eval(attrs.onclick) : undefined}                     >                       {node.meta?.label?.text || "Sign in with passkey"}                     </button>                     {messages}                   </div>                 )               default:                 return (                   <div key={idx}>                     {label}                     <input                       id={attrs.name}                       name={attrs.name}                       type={attrs.type}                       value={attrs.value || ""}                       required={attrs.required}                       disabled={attrs.disabled}                     />                     {messages}                   </div>                 )             }           }           return null         })}       </form>     </div>   ) }  export default PasskeyLogin
## HTML Setup Make sure to include the WebAuthn script in your HTML:
Copy code
html <head>   <script src="/.well-known/ory/webauthn.js"></script> </head>
## Ory Configuration To enable passkey authentication in your Ory project, add this to your configuration:
Copy code
yaml selfservice:   methods:     passkey:       enabled: true       config:         rp:           display_name: "Your Application Name"           id: "<http://your-domain.com|your-domain.com>"           origins:             - "<https://your-domain.com>"   flows:     registration:       after:         passkey:           hooks:             - hook: session  # Automatically log users in after registration
## How Passkey Authentication Works 1. Registration: - User initiates registration - Browser prompts user to create a passkey - User authenticates with biometrics (FaceID, TouchID, Windows Hello, etc.) - A new credential is created and stored 2. Login: - User clicks "Sign in with passkey" - Browser prompts user to select a passkey - User authenticates with biometrics - User is logged in Passkeys provide a more
b
@chilly-king-10285 webauthn.js is loaded but receiving the following error. react-dom.development.js:4310 Uncaught TypeError: window.oryPasskeyRegistration is not a function
b
A message from kapa.ai
I can help you troubleshoot this error with the WebAuthn implementation in Ory. The error "window.oryPasskeyRegistration is not a function" typically occurs when the WebAuthn JavaScript file hasn't fully loaded or initialized before you're trying to access the function. Here are some steps to resolve this issue: 1. Ensure proper script loading: Make sure the WebAuthn script is properly loaded before you try to use it. The script should be included in your HTML as shown in the [Ory documentation](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-advanced-integration#passwordless-authentication):
Copy code
html    <head>        <script src="/.well-known/ory/webauthn.js"></script>      </head>
2. Check for script loading errors: Open your browser's developer console and check if there are any errors related to loading the script. The URL might be incorrect or the script might be blocked. 3. Use the correct function name: Make sure you're using the correct function name. In newer versions of Ory, the function might be named
__oryPasskeyRegistration
(with double underscores) instead of
oryPasskeyRegistration
. 4. Wait for script to load: Add a check to ensure the script is fully loaded before trying to use the function:
Copy code
javascript    // Add this to your component    useEffect(() => {      const checkWebAuthnLoaded = setInterval(() => {        if (window.__oryPasskeyRegistration) {          clearInterval(checkWebAuthnLoaded);          console.log("WebAuthn script loaded successfully");        }      }, 100);            // Clear interval after 10 seconds to prevent infinite checking      setTimeout(() => clearInterval(checkWebAuthnLoaded), 10000);            return () => clearInterval(checkWebAuthnLoaded);    }, []);
5. Check for trigger attributes: Make sure your UI nodes have the correct
onclickTrigger
or
onloadTrigger
attributes. The error might occur if you're trying to call the function directly instead of letting Ory's UI nodes handle it. 6. Verify Ory version compatibility: Ensure you're using the correct version of the Ory client that matches your Ory server version. The function names and behavior might differ between versions. If you're still experiencing issues, you might want to try using the
onclickTrigger
approach instead of directly calling the function:
Copy code
javascript // Instead of directly calling window.oryPasskeyRegistration() // Use the UI node with the correct trigger const passkeyButton = flow.ui.nodes.find(   node =>      isUiNodeInputAttributes(node.attributes) &&      node.attributes.onclickTrigger === "oryPasskeyRegistration" );  // Then render this button in your UI
This approach lets Ory handle the function calling internally, which can be more reliable.
b
@chilly-king-10285 - I see the following error
This browser does not support WebAuthn!
using Chrome
b
A message from kapa.ai
I can help you troubleshoot the "This browser does not support WebAuthn!" error you're encountering in Chrome. This is unexpected since Chrome does support WebAuthn/Passkeys. There are several potential causes for this error: 1. Relying Party ID mismatch: This is a common issue when using WebAuthn or Passkeys. The error occurs when the domain you're using doesn't match the configured Relying Party ID in your Ory configuration. As mentioned in the [Ory troubleshooting documentation](https://www.ory.sh/docs/troubleshooting/passkeys-webauthn-security-error#relying-party-id-mismatch), if you're using Ory Tunnel, Ory Proxy, or other tools that proxy requests to a different domain, you might encounter this issue. To fix this for local development, you can update the RP ID and origins in your Ory configuration:
Copy code
# For Passkeys    ory patch identity-config --project <project-id> --workspace <workspace-id> \      --replace "/selfservice/methods/passkey/config/rp/id=\"localhost\"" \      --replace "/selfservice/methods/passkey/config/rp/origins=[\"<http://localhost:3000\>"]" \      --format yaml
2. HTTPS requirement: WebAuthn typically requires HTTPS in production environments. For local development, it only works on
localhost
or with properly configured HTTPS. 3. Browser settings: Make sure you haven't disabled WebAuthn in your Chrome settings. 4. Chrome version: Ensure you're using a recent version of Chrome that fully supports WebAuthn. 5. Secure context: WebAuthn only works in secure contexts (HTTPS or localhost). 6. Cross-origin issues: If you're using iframes or cross-origin requests, this could cause WebAuthn to fail. To further diagnose the issue, check your browser console for additional error messages that might provide more specific information about why WebAuthn is failing. If you're testing locally, make sure you're using
localhost
as your domain, as this is one of the few non-HTTPS domains where WebAuthn is allowed to work.
b
@chilly-king-10285 - "no authentication handler was responsible for handling authentication request"
b
A message from kapa.ai
I see you're encountering the error "no authentication handler was responsible for handling authentication request" with Ory. This error typically occurs when none of the configured authentication methods in Ory can process the authentication request. This error can happen in several scenarios: 1. WebAuthn/Passkey Configuration Issues: Based on our previous conversation about WebAuthn and passkeys, this error might be related to your WebAuthn implementation. If the WebAuthn authentication handler is not properly configured or if there's a mismatch in the Relying Party ID settings, you might see this error. 2. Missing Authentication Methods: If you haven't properly enabled the authentication methods you're trying to use (like passkeys, password, or one-time codes), Ory won't be able to find a suitable handler. 3. Oathkeeper Configuration: If you're using Ory Oathkeeper, this error can occur when none of the configured authenticators in your access rules can handle the provided credentials. As mentioned in the [Oathkeeper documentation](https://www.ory.sh/docs/oathkeeper/pipeline/authn), the first authenticator that's able to handle the credentials will be consulted, and if none can handle them, this error occurs. To resolve this issue: 1. Check your authentication configuration: Make sure you've properly enabled the authentication methods you want to use in your Ory configuration. 2. Verify WebAuthn settings: If using WebAuthn/passkeys, ensure your configuration has the correct domain settings as we discussed earlier. 3. Review access rules: If using Oathkeeper, check your access rules to ensure they include appropriate authenticators for your use case. 4. Check browser console: Look for additional error messages in your browser console that might provide more specific information about the authentication failure. 5. Session persistence: If you're using OAuth2 flows, make sure your sessions are properly persisted. As mentioned in a [GitHub issue](https://github.com/ory/network/issues/194), Ory Kratos does not persist sessions unless
sessions.cookie.persistent
is enabled. If you can provide more context about your specific implementation and where exactly this error is occurring, I can give you more targeted troubleshooting steps.
c
@chilly-king-10285 - how can i set the redirection of a passkey login flow
b
A message from kapa.ai
I can help you set up redirection for a passkey login flow in Ory. There are several ways to handle redirects after a successful passkey authentication. ## Setting Redirection for Passkey Login Flow ### 1. Using the
return_to
Query Parameter The simplest way to set a redirect URL for a specific login attempt is to use the
return_to
query parameter when initializing the login flow:
Copy code
javascript // When initializing the login flow frontend.createBrowserLoginFlow({   return_to: "<https://your-app.com/dashboard>" })
This approach works for any authentication method, including passkeys, as described in the [Browser redirects and flow completion documentation](https://www.ory.sh/docs/concepts/redirects). ### 2. Configuration-Based Redirects You can also configure default redirects in your Ory configuration:
Copy code
yaml selfservice:   flows:     login:       after:         default_browser_return_url: <https://your-app.com/default-page>         passkey:           default_browser_return_url: <https://your-app.com/passkey-specific-page>
This configuration sets a specific redirect URL for passkey authentication that overrides the default redirect URL, as explained in the [HTTP redirection configuration documentation](https://www.ory.sh/docs/kratos/concepts/browser-redirect-flow-completion). ### 3. For API Clients (AJAX) If you're using an API client like AJAX instead of a traditional browser form submission, you'll need to handle the redirect in your code:
Copy code
javascript // After successful login .then((res) => {   router.push('/your-redirect-path') })
As mentioned in the [troubleshooting documentation for API clients](https://www.ory.sh/docs/concepts/redirects#api-clients), browser redirects only work for regular browser requests, not for API clients. ### 4. Allowed Return URLs For security reasons, Ory prevents open redirect attacks by validating redirect URLs against an allow list. Make sure your redirect domain is in the allowed list:
Copy code
yaml selfservice:   allowed_return_urls:     - <https://your-app.com/>
This is important when using dynamic redirects with the
return_to
parameter, as explained in the [allow list documentation](https://www.ory.sh/docs/concepts/redirects#allow-list). ## Important Notes 1. Make sure your redirect URLs are valid with a proper scheme (HTTP or HTTPS). 2. You cannot set Ory-owned domains as redirect URLs. 3. For local development, make sure your configuration matches your testing environment. If you're experiencing issues with redirects after passkey authentication, check that your configuration is correct and that you're handling the response appropriately based on whether you're using browser forms or API clients.
c
@chilly-king-10285 how can logout after a passkey login
b
A message from kapa.ai
I'll explain how to implement logout functionality after a passkey login with Ory. There are several approaches depending on your application type. ## Browser-Based Applications For browser applications where you're using passkeys (WebAuthn) for authentication, you can implement logout using the following methods: ### 1. Using the Browser Logout Flow
Copy code
javascript import { Configuration, FrontendApi } from "@ory/client"  const frontend = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>", // Use your Ory Tunnel URL or project URL     baseOptions: {       withCredentials: true,     },   }), )  // Create a logout flow and get the logout URL async function logout() {   try {     // Create a "logout flow" in Ory     const { data: flow } = await frontend.createBrowserLogoutFlow()          // Option 1: Redirect to the logout URL     window.location.href = flow.logout_url          // Option 2: Use the token to perform the logout via AJAX     // await frontend.updateLogoutFlow({     //   token: flow.logout_token,     // })     // Then redirect or update UI as needed   } catch (error) {     console.error("Logout failed:", error)   } }
This approach works for any authentication method, including passkeys, as shown in the [Kratos Self-service Flows documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-logout#single-page-application-spa). ### 2. React Example with Logout Button
Copy code
javascript import { Configuration, FrontendApi } from "@ory/client" import { useState } from "react"  const frontend = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>",     baseOptions: {       withCredentials: true,     },   }), )  export function LogoutButton() {   const [logoutUrl, setLogoutUrl] = useState(null)      // Get the logout URL when component mounts   useEffect(() => {     frontend.createBrowserLogoutFlow()       .then(({ data }) => {         setLogoutUrl(data.logout_url)       })       .catch(err => console.error(err))   }, [])      return (     <a href={logoutUrl}>Logout</a>   ) }
This example is similar to the approach shown in the [React integration documentation](https://www.ory.sh/docs/getting-started/integrate-auth/react#require-login-to-access-the-app-page). ## Native/API Applications If you're using passkeys in a native application or API client that uses session tokens:
Copy code
javascript import { Configuration, FrontendApi } from "@ory/client"  const frontend = new FrontendApi(   new Configuration({     basePath: `https://${process.env.ORY_PROJECT_SLUG}.<http://projects.oryapis.com|projects.oryapis.com>`,   }), )  export async function logout(token) {   return await frontend.performNativeLogout({     performNativeLogoutBody: {       session_token: token,     },   }) }
This approach is documented in the [Native API applications section](https://www.ory.sh/docs/kratos/self-service/flows/user-logout#native-api-applications) of the Ory documentation. ## OAuth2/OpenID Connect Logout If you're using Ory OAuth2 & OpenID Connect with passkeys, you'll need to use the OIDC logout endpoint:
Copy code
javascript // Redirect to the OIDC logout endpoint window.location.href = `https://${projectSlug}.<http://projects.oryapis.com/oauth2/sessions/logout|projects.oryapis.com/oauth2/sessions/logout>`
This endpoint initiates and completes user logout at the Ory OAuth2 & OpenID provider as described in the [OpenID Connect logout documentation](https://www.ory.sh/docs/oauth2-oidc/oidc-logout). ## Important Notes 1. The logout process is the same regardless of which authentication method (passkeys, passwords, etc.) was used to log in. 2. For browser applications, make sure to handle cookies properly by including
withCredentials: true
. 3. For native applications, you need to store and provide the session token. 4. You can configure post-logout redirects in your Ory configuration or by using the
return_to
parameter. Let me know if you need more specific implementation details for your particular use case!
c
@chilly-king-10285 - how to check and restrict email domains on a passkey loging
b
A message from kapa.ai
I'll explain how to check and restrict email domains on a passkey login flow in Ory. This can be accomplished using Ory Actions (hooks) that run before the login flow is processed. ## Restricting Email Domains with Ory Actions You can implement domain restrictions for passkey login by using pre-login hooks that validate the email domain before allowing the authentication to proceed. ### 1. Using Ory Actions (Hooks) Ory provides hooks that can run before login flows. You can use these to check and restrict email domains:
Copy code
yaml selfservice:   flows:     login:       before:         hooks:           - hook: web_hook             config:               url: <https://your-api.com/validate-domain>               method: POST               auth:                 type: api_key                 config:                   name: Authorization                   value: Bearer your-secret-token                   in: header
This configuration sends login data to your webhook endpoint before processing the login flow, as described in the [Hook Documentation](https://www.ory.sh/docs/kratos/hooks/configure-hooks). ### 2. Implementing the Webhook Logic Your webhook endpoint should validate the email domain and return an appropriate response:
Copy code
javascript // Example webhook implementation <http://app.post|app.post>('/validate-domain', (req, res) => {   const { email } = req.body.traits || {};      if (!email) {     return res.status(400).json({       error: {         message: "Email is required"       }     });   }      // Extract domain from email   const domain = email.split('@')[1];      // List of allowed domains   const allowedDomains = ['<http://company.com|company.com>', '<http://approved-partner.com|approved-partner.com>'];      if (!allowedDomains.includes(domain)) {     return res.status(403).json({       error: {         message: "Your email domain is not authorized to access this application"       }     });   }      // Allow the login to proceed   return res.status(200).json({}); });
### 3. Flow-Interrupting Webhooks For more advanced control, you can use flow-interrupting webhooks that can completely block the login process:
Copy code
yaml selfservice:   flows:     login:       before:         hooks:           - hook: web_hook             config:               url: <https://your-api.com/validate-domain>               method: POST               can_interrupt: true               auth:                 type: api_key                 config:                   name: Authorization                   value: Bearer your-secret-token                   in: header
With
can_interrupt: true
, your webhook can return a specific error response to prevent the login from proceeding, as explained in the [Flow-interrupting webhooks documentation](https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#flow-interrupting-webhooks). ### 4. Using Ory CLI to Configure You can also set up these hooks using the Ory CLI:
Copy code
bash ory patch identity-config {project_id} \   --add '/selfservice/flows/login/before/hooks=[{"hook": "web_hook", "config": {"url": "<https://your-api.com/validate-domain>", "method": "POST", "can_interrupt": true}}]'
## Important Notes 1. This approach works for all authentication methods, including passkeys. 2. The webhook needs to be secure and reliable as it will be called for every login attempt. 3. For better user experience, consider showing clear error messages when domain validation fails. 4. You can implement more complex logic in your webhook, such as checking against a database of allowed domains or integrating with your organization's directory service. By implementing these domain restrictions, you can ensure that only users with approved email domains can authenticate to your application using passkeys or any other authentication method. [Ory Actions](https://www.ory.sh/docs/identities/sign-in/actions#customize-login-and-registration-behavior) provides a flexible way to customize login behavior, including domain restrictions and other validation rules.
c
@chilly-king-10285 - can I append to the traits on a passkey login
b
A message from kapa.ai
Yes, you can append to identity traits during a passkey login flow in Ory by using webhooks. This allows you to modify or add data to the user's identity when they authenticate with a passkey. ## Using Webhooks to Modify Traits The most effective way to append to traits during passkey login is to use a webhook that runs after the login flow:
Copy code
yaml selfservice:   flows:     login:       after:         passkey:           hooks:             - hook: web_hook               config:                 url: <https://your-api.com/update-traits>                 method: POST                 response:                   parse: true
With
response.parse: true
, your webhook can return data that modifies the identity, as explained in the [Webhook response handling documentation](https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#modify-identities). ## Webhook Implementation Your webhook should return a JSON response that includes the updated traits:
Copy code
javascript // Example webhook implementation <http://app.post|app.post>('/update-traits', (req, res) => {   // Get the current identity from the request   const { identity } = req.body;      // Get the current traits   const currentTraits = identity.traits || {};      // Append new data to traits   const updatedTraits = {     ...currentTraits,     last_login_timestamp: new Date().toISOString(),     login_count: (currentTraits.login_count || 0) + 1,     // Add any other data you want to append   };      // Return the updated identity   res.status(200).json({     identity: {       traits: updatedTraits     }   }); });
## Important Considerations 1. Complete Data Return: When updating traits, you must return the complete traits object, not just the fields you want to update. As mentioned in the [Modify identities documentation](https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#modify-identities), if you only return partial data, the missing fields will be removed. 2. Schema Validation: Any traits you add must conform to your identity schema. Make sure your schema allows for the additional fields you're adding. 3. Metadata Alternative: For data that doesn't need to be user-editable, consider using identity metadata instead of traits:
Copy code
javascript    res.status(200).json({      identity: {        traits: currentTraits, // Keep existing traits        metadata_public: {          // Public metadata visible to the user          last_login: new Date().toISOString()        },        metadata_admin: {          // Admin-only metadata          login_statistics: {            count: (identity.metadata_admin?.login_statistics?.count || 0) + 1          }        }      }    });
This approach allows you to store additional information without modifying the user-editable traits, as described in the [Update identity metadata documentation](https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#update-identity-metadata). By implementing these webhooks, you can effectively append data to user traits or metadata during passkey login flows.
b
@chilly-king-10285 how do i create an api key for a self hosted instance
b
A message from kapa.ai
To create an API key for a self-hosted Ory instance, you'll need to follow different approaches depending on which Ory component you're using. Unlike Ory Network, which has a built-in API key management system through the console, self-hosted Ory instances require different methods for API access control. ## API Access Control for Self-Hosted Ory Self-hosted Ory servers don't come with integrated access control by default. As mentioned in the [Ory documentation](https://www.ory.sh/docs/self-hosted/operations/api-access-control): > The APIs of Ory open-source Servers don't come with integrated access control. This means that all requests sent to their APIs are considered authenticated, authorized, and will be executed. Leaving the APIs in this state can lead to severe security risks. For proper API access control in self-hosted environments, you should: 1. Use [Ory Oathkeeper](https://github.com/ory/oathkeeper) or a comparable API Gateway to protect access to your Ory APIs 2. Implement proper authentication and authorization mechanisms ## Creating API Keys with Ory Hydra If you're using Ory Hydra (OAuth2 server), you can create API keys by setting up OAuth2 clients with the client credentials flow: 1. Create an OAuth2 client:
Copy code
hydra create client \   --endpoint <http://your-hydra-admin-api/> \   --id my-api-client \   --secret my-api-secret \   --grant-types client_credentials \   --response-types token \   --scope read,write
2. Your client ID and secret will function as your API key ## Using JSON Web Keys (JWKs) For more advanced scenarios, you can create JSON Web Keys (JWKs) that can be used for signing tokens:
Copy code
hydra create jwks --endpoint=<http://your-hydra-admin-api/> my-key-set --alg RS256
You can also set your own JSON Web Keys using the API:
Copy code
PUT /admin/keys/{set}
As described in the [Hydra API Reference](https://www.ory.sh/docs/hydra/reference/api), this allows you to save your own keys instead of letting Hydra generate them for you. ## Best Practices for Self-Hosted API Security 1. Protect admin APIs: Always place admin APIs behind proper authentication 2. Use HTTPS: Ensure all API communication happens over HTTPS 3. Rotate keys: Implement key rotation for security as described in the [Secrets and key rotation documentation](https://www.ory.sh/docs/hydra/self-hosted/secrets-key-rotation) 4. Use token prefixes: Ory uses specific prefixes for different token types (e.g.,
ory_at_
for access tokens) which helps with security scanning If you need more specific guidance for your particular self-hosted setup, I recommend reaching out to the community on [Ory Community Slack](http://slack.ory.sh/) as suggested in the documentation.
c
@chilly-king-10285 What are the different responses types for hydra create and their uses
b
A message from kapa.ai
I'll explain the different response types available when creating OAuth2 clients with Ory Hydra and their specific uses. ## OAuth2 Response Types in Ory Hydra When creating an OAuth2 client in Ory Hydra, you can specify different response types using the
--response-type
flag. Based on the [Hydra CLI documentation](https://www.ory.sh/docs/hydra/cli/hydra-create-oauth2-client#options), the main response types are: ### 1.
code
(Authorization Code)
Copy code
ory create oauth2-client --response-type code
Uses: - Most secure flow for server-side applications - Provides an authorization code that must be exchanged for tokens - Suitable for web applications with a backend server - Default response type if not specified ### 2.
token
(Implicit Flow)
Copy code
ory create oauth2-client --response-type token
Uses: - Provides access tokens directly in the redirect URI fragment - Historically used for single-page applications (SPAs) - Less secure than the authorization code flow - Generally not recommended for new applications ### 3.
id_token
(OpenID Connect)
Copy code
ory create oauth2-client --response-type id_token
Uses: - Returns an ID token containing user identity information - Used for authentication-only flows - Part of OpenID Connect specification ### 4. Combination Types You can also combine response types for more complex flows:
Copy code
ory create oauth2-client --response-type "code id_token"
Uses: - Hybrid flows that combine aspects of multiple flows - Provides both authorization code and ID token in a single response - Useful for applications that need immediate access to identity information while still using the more secure code flow ## Example Client Configurations As shown in the [Ory documentation](https://www.ory.sh/docs/hydra/guides/oauth2-clients#example-oauth2-clients), here's an example of creating a client with specific response types:
Copy code
ory create oauth2-client \     --grant-type authorization_code --grant-type refresh_token \     --response-type code \     --scope openid --scope offline_access \     --token-endpoint-auth-method client_secret_post \     --redirect-uri <https://my-app.com/callback>
This creates a client that uses the authorization code flow (
code
response type) with refresh token support. When choosing response types, consider your application architecture, security requirements, and the specific OAuth2/OpenID Connect features you need to implement.
c
@chilly-king-10285 which token is best suited for web to API calls
b
A message from kapa.ai
For web to API calls with Ory, the best token type depends on your specific security requirements and architecture. Let me explain the options: ## JWT vs. Opaque Access Tokens Ory offers two main types of access tokens for API authorization: 1. Opaque Access Tokens (default): - Random strings with no inherent meaning - Require validation through introspection API - Provide immediate revocation capability - Example:
ory_at_JGhESDjKfHMQ8Wcy0cC3.hIQxGmX37ydn8WmKAnlD3U
2. JWT Access Tokens: - Self-contained tokens with embedded claims - Can be validated without a database lookup - Faster validation (~1ms vs ~50ms for opaque tokens) - Revocation is eventual rather than immediate As noted in the [Ory documentation](https://www.ory.sh/docs/oauth2-oidc/jwt-access-token), opaque tokens have an advantage for revocation, while JWTs have an advantage for performance. ## Recommendation for Web to API Calls For most web-to-API scenarios, opaque tokens are recommended because: 1. They provide immediate revocation capability 2. They don't expose potentially sensitive information in the token itself 3. They're the default in Ory for security reasons However, if your architecture requires high-performance API calls with minimal latency and you can tolerate eventual (rather than immediate) token revocation, JWT access tokens may be more suitable. ## Important Considerations 1. Token Usage: As mentioned in the [Ory FAQ](https://www.ory.sh/docs/hydra/faq#should-i-use-oauth2-tokens-for-authentication), OAuth2 tokens should be used for authorization to APIs, not for user authentication. For authentication, use OpenID Connect ID Tokens. 2. Alternative Approaches: For simple web applications that don't need third-party API access, you might not need OAuth2 at all. As noted in the [Ory documentation](https://www.ory.sh/docs/security-model#can-i-use-oauth-20--openid-connect), session cookies might be a simpler approach for your own web applications. 3. Configuration: If you decide to use JWT tokens, you can configure them either globally or per-client as shown in the [JWT access token documentation](https://www.ory.sh/docs/oauth2-oidc/jwt-access-token#jwt-access-tokens). For most web-to-API scenarios where security is a priority, opaque tokens provide the best balance of security and functionality.
c
@chilly-king-10285 can I exchange the Ory session cookie for an opaque token?
b
A message from kapa.ai
Yes, you can exchange an Ory session cookie for an opaque token. This is useful when you need to make API calls from a browser-based application that initially authenticates with cookies but needs to communicate with APIs that expect token-based authentication. There are two main approaches to accomplish this: ## 1. Using the Session Token Exchange Endpoint Ory provides a dedicated endpoint for exchanging session tokens:
Copy code
GET /sessions/token-exchange
This endpoint allows you to exchange session information between different authentication contexts, as documented in the [Ory API Reference](https://www.ory.sh/docs/reference/api). You'll need to provide the appropriate parameters (
init_code
and
return_to_code
) to complete the exchange. ## 2. Using the Sessions Whoami Endpoint The more common approach is to use the
/sessions/whoami
endpoint, which can return the session token when called from a browser with an active session cookie:
Copy code
javascript import { Configuration, FrontendApi } from "@ory/client"  const frontend = new FrontendApi(   new Configuration({     basePath: "<https://your-project.projects.oryapis.com>",     baseOptions: {       withCredentials: true, // Important for sending cookies     },   }), )  // Get session information and token const { data } = await frontend.toSession() const sessionToken = data.session_token
As described in the [Ory documentation](https://www.ory.sh/docs/identities/sign-in/check-session-token-cookie-api), the
/sessions/whoami
endpoint checks for both cookies and tokens, making it versatile for different authentication scenarios. ## Important Notes 1. The session token has a format like
MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj
and can be used in the
Authorization
header for subsequent API calls. 2. For security reasons, Ory recommends using session cookies for browser-based applications and session tokens for non-browser applications, as mentioned in the [session management documentation](https://www.ory.sh/docs/kratos/session-management/overview). 3. If you're looking for OAuth2 tokens specifically (like access tokens or refresh tokens), you would need to use the OAuth2 token endpoint as described in the [API reference](https://www.ory.sh/docs/reference/api), but this is a different flow from session token exchange.
c
@chilly-king-10285 when would I use a session token vs an opaque token
b
A message from kapa.ai
In Ory, session tokens and opaque tokens serve different purposes in authentication and authorization flows. Let me explain when to use each: ## Session Tokens Session tokens (prefixed with
ory_st_
) are used to maintain user authentication state and are primarily used for: 1. Native/Mobile Applications: When you can't use cookies, session tokens provide a way to maintain authentication state.
Copy code
javascript // Example of using a session token in a native app const session = await frontend.toSession({   xSessionToken: "your-session-token" })
2. Server-to-Server Communication: When your backend needs to make authenticated requests on behalf of a user. 3. Non-Browser Clients: As mentioned in the [Ory documentation](https://www.ory.sh/docs/identities/native-browser), "Native apps use the
https://$<http://PROJECT_SLUG.projects.oryapis.com/self-service/{flow-type}/api|PROJECT_SLUG.projects.oryapis.com/self-service/{flow-type}/api>
endpoint... Ory issues an Ory Session Token instead of an Ory Session Cookie." ## Opaque Tokens (OAuth2 Access Tokens) Opaque tokens (prefixed with
ory_at_
) are OAuth2 access tokens used for API authorization and are best suited for: 1. API Access Control: When you need to authorize access to protected resources/APIs. 2. Third-Party Integration: When your application needs to access third-party services on behalf of a user. 3. Microservices Architecture: When different services need to communicate with varying levels of access. 4. Web-to-API Calls: As you asked in a previous question, opaque tokens are generally recommended for web-to-API calls because they provide immediate revocation capability. ## Key Differences 1. Purpose: - Session tokens represent user authentication state - Opaque tokens represent authorization to access specific resources 2. Issuance: - Session tokens are issued after user authentication - Opaque tokens are issued through OAuth2 flows (authorization code, client credentials, etc.) 3. Security Model: - Session tokens typically have longer lifespans - Opaque tokens are designed for shorter-lived, specific authorizations 4. Revocation: - Both can be immediately revoked, unlike JWT tokens which have eventual revocation As noted in the [Ory security compliance documentation](https://www.ory.sh/docs/security-compliance/token-formats), these different token types have specific prefixes to make them easily identifiable for security purposes. In summary, use session tokens for maintaining authentication state across requests, especially in non-browser environments, and use opaque tokens when you need to authorize access to specific APIs or resources.
c
@chilly-king-10285 best practice to determine if a user can an API endpoint
b
A message from kapa.ai
I'll explain the best practices for determining if a user can access an API endpoint in Ory. ## Best Practices for API Authorization in Ory Ory provides several approaches to check if a user has permission to access an API endpoint. The recommended approach depends on your architecture and security requirements. ### 1. Using Ory Permissions (formerly Keto) For fine-grained access control, Ory Permissions is the recommended solution:
Copy code
javascript // Example permission check const isAllowed = await ory   .permission   .checkPermission({     namespace: "files",      object: "document-123",      relation: "view",     subjectId: "user:john@example.com"   })   .then(({data}) => data.allowed)
This approach allows you to define complex relationship-based permissions as described in the [Ory Permissions documentation](https://www.ory.sh/docs/guides/permissions/overview#checking-for-permissions). ### 2. Using OAuth2 Access Tokens with Scopes For API authorization using OAuth2: 1. Protect your API with access token validation 2. Check the token's scopes to determine permissions
Copy code
javascript // In your API middleware function checkScope(requiredScope) {   return (req, res, next) => {     // Validate the token and check if it has the required scope     const token = req.headers.authorization?.split(' ')[1]          // Introspect the token to verify it and get its scopes     ory.introspectOAuth2Token({       token: token     }).then(({data}) => {       if (data.active && data.scope.includes(requiredScope)) {         next()       } else {         res.status(403).json({ error: "Insufficient permissions" })       }     })   } }  // Usage app.get('/api/resource', checkScope('read:resource'), (req, res) => {   // Handle the request })
### 3. Best Practices According to the [Ory documentation](https://www.ory.sh/docs/keto/guides/simple-access-check-guide#best-practices), you should: 1. Don't cache permission checks: Ory provides a low-latency system with appropriate caching. Incorrect caching can lead to security vulnerabilities. 2. Perform permission checks in every critical path: Always check permissions before allowing access to protected resources.
Copy code
javascript    // Before any sensitive operation    const allowed = await ory.permission.checkPermission({...})    if (!allowed) {      return res.status(403).send("Permission denied")    }
3. Use fine-grained permissions: Define permissions at the appropriate level of granularity to enforce the principle of least privilege. 4. Audit permissions regularly: Review and audit permissions to ensure users only have the access they need. ### 4. Synchronous Authorization Flow As described in the [Ory documentation](https://www.ory.sh/docs/keto/guides/simple-access-check-guide#synchronous-authorization-flow): > It's crucial that all permission checks are performed using Ory Permissions. This means that before any action is taken, the required permission should be checked to ensure that the action is allowed. For a complete implementation, you should: 1. Authenticate the user (using Ory Identities or OAuth2) 2. Extract the subject identifier 3. Check permissions using Ory Permissions 4. Allow or deny access based on the permission check result This approach ensures consistent and secure access control across your application. Would you like me to elaborate on any specific aspect of API authorization with Ory?
c
@chilly-king-10285 with Oathkeeper does that handle token exchanges
b
A message from kapa.ai
Yes, Ory Oathkeeper can handle token exchanges as part of its identity and access proxy functionality. Oathkeeper acts as an API gateway that can transform authentication credentials from one format to another. ## Token Exchange with Oathkeeper Oathkeeper uses a pipeline of authenticators, authorizers, and mutators to process requests. The mutator component is specifically responsible for transforming authentication information into different token formats. As described in the [Ory documentation](https://www.ory.sh/docs/kratos/guides/zero-trust-iap-proxy-identity-access-proxy#id-token-mutator), Oathkeeper can transform session cookies into JWT tokens:
Copy code
yaml mutators:   id_token:     enabled: true     config:       issuer_url: <http://127.0.0.1:4455/>       jwks_url: file:///etc/config/oathkeeper/id_token.jwks.json       claims: |         {           "session": {{ .Extra | toJson }}         }
This configuration allows Oathkeeper to take an Ory Session Cookie and transform it into a JWT that contains the session information. ## Common Token Exchange Scenarios 1. Cookie to JWT: Converting Ory Session Cookies to JWTs for API access
Copy code
Cookie: ory_kratos_session=... → Authorization: bearer eyJhbGci...
2. Session to Header: Converting session information to custom headers
Copy code
Cookie: ory_kratos_session=... → X-User-ID: user-123
3. OAuth2 Token to JWT: Transforming OAuth2 tokens to standardized JWTs As mentioned in the [Ory Proxy documentation](https://www.ory.sh/docs/guides/cli/proxy-and-tunnel#use-ory-proxy-with-json-web-tokens), Oathkeeper (and the deprecated Ory Proxy) can translate Ory credentials to JSON Web Tokens, making it easier to integrate with various backend services. ## Mutator Types for Token Exchange Oathkeeper offers several mutators for different token exchange needs: 1. ID Token Mutator: Converts authentication info to a JWT 2. Header Mutator: Adds authentication data to HTTP headers 3. Cookie Mutator: Sets cookies based on authentication data 4. NoOp Mutator: Passes authentication data unchanged For web-to-API scenarios, the ID Token Mutator is particularly useful as it allows browser applications using cookies to communicate with APIs expecting token-based authentication. Would you like more specific information about implementing a particular token exchange pattern with Oathkeeper?
b
@chilly-king-10285 - can you provide a simple example of using oathkeeper and a kubernetes
b
A message from kapa.ai
Certainly! Here’s a simple example of using Ory Oathkeeper with Kubernetes, based on the available knowledge sources. ## Example: Oathkeeper in a Kubernetes Pod A user reported running Oathkeeper in a Kubernetes pod, describing their configuration and experience. Here’s a summary of the setup: - Oathkeeper runs as a container in a pod. - Configuration is provided via environment variables or mounted config files. - Access rules are defined in a JSON file and mounted into the container. ### Example Oathkeeper Rule (from user’s Kubernetes setup)
Copy code
json [   {     "id": "public",     "version": "v0.36.0-beta.4",     "upstream": {       "url": "$RESOURCE_SERVER",       "preserve_host": true     },     "match": {       "url": "<https?://[^/]+/creditors/(\\.docs(/.*)?|.*[./-]public)$>",       "methods": [         "GET", "HEAD"       ]     },     "authenticators": [       {         "handler": "noop"       }     ],     "authorizer": {       "handler": "allow"     },     "mutators": [       {         "handler": "noop"       }     ]   } ]
This rule allows public (unauthenticated) access to certain endpoints and forwards requests to the upstream service. ### Running Oathkeeper in Kubernetes - Pod Specification: The Oathkeeper container is started with a memory limit (e.g., 200Mb). - Config and Rules: Configuration and rules are mounted into the container, typically via ConfigMaps or Secrets. - Liveness Probes: Kubernetes liveness probes can be configured to hit a public rule endpoint to check health. Note: The user observed that Oathkeeper’s memory usage could vary on startup, and setting a memory limit in the pod spec helped manage resource usage. The pod would restart if Oathkeeper exceeded the limit, but would eventually start successfully and function as expected. > “When I set a 200Mb memory limit for the pod, the pod never consumes more than 200Mb (it is around 120Mb in my case: 50Mb for Oathkeeper and 70Mb for my other processes). However, I get
Back-off restarting failed container web-server in pod XXXXX
Kubernetes events when re-creating the pod with the same frequency (about 50%).” > — [GitHub Issue: 600Mb of memory consumed on the start](https://github.com/ory/oathkeeper/issues/1230) ### Summary - Deploy Oathkeeper as a container in your Kubernetes pod. - Mount your configuration and access rules. - Use Kubernetes probes for health checks. - Set resource limits as needed. This is a basic example, but it reflects a real-world Oathkeeper + Kubernetes deployment as described in the knowledge sources. For more advanced setups, you would typically use ConfigMaps, Secrets, and possibly sidecar containers for upstream services or logging. If you need a full Kubernetes manifest or more details, let me know!
b
@chilly-king-10285 - how does this work with a kubernetes ingress
b
A message from kapa.ai
When using Ory Oathkeeper with a Kubernetes Ingress, Oathkeeper typically acts as a reverse proxy or policy enforcement point in front of your upstream services. In a Kubernetes environment, you would deploy Oathkeeper as a service (or sidecar) and configure your Ingress to route traffic through Oathkeeper before it reaches your backend services. However, the knowledge sources do not provide a detailed, step-by-step example of integrating Oathkeeper directly with Kubernetes Ingress resources. They do confirm that Oathkeeper is commonly run as a container in a Kubernetes pod, with configuration and access rules mounted via ConfigMaps or Secrets, and that it can be used as a reverse proxy in front of your services. The Ingress would typically be configured to send requests to the Oathkeeper service, which then applies its access rules and forwards allowed requests to the appropriate upstream service within your cluster. If you need to integrate Oathkeeper with an Ingress controller, you would generally: 1. Deploy Oathkeeper as a service in your cluster. 2. Configure your Ingress to forward requests to the Oathkeeper service instead of directly to your backend. 3. Oathkeeper applies its access rules and, if the request is allowed, proxies it to the actual backend service. The knowledge sources do not provide a concrete YAML example or specific Ingress annotations for this setup, so if you need exact configuration details, you may need to consult additional documentation or community resources. In summary: Oathkeeper can be used with Kubernetes Ingress by acting as a reverse proxy between the Ingress and your backend services, but the knowledge sources do not provide a detailed integration example or configuration for this scenario.
b
@chilly-king-10285 - does this setup look correct.
Copy code
❯ k describe ingress internal -n auth-c0
Name:             internal
Labels:           <http://app.kubernetes.io/component=api|app.kubernetes.io/component=api>
                  <http://app.kubernetes.io/instance=auth-c0-vwg-stg-1-ew1|app.kubernetes.io/instance=auth-c0-vwg-stg-1-ew1>
                  <http://app.kubernetes.io/managed-by=Helm|app.kubernetes.io/managed-by=Helm>
                  <http://app.kubernetes.io/name=auth|app.kubernetes.io/name=auth>
                  <http://app.kubernetes.io/version=main-bcd7ffac|app.kubernetes.io/version=main-bcd7ffac>
                  cell=cell0
                  <http://helm.sh/chart=auth-1.1.0-bcd7ffac|helm.sh/chart=auth-1.1.0-bcd7ffac>
Namespace:        auth-c0
Address:          <http://ingress-internal-b9753b17e64918a9.elb.eu-west-1.amazonaws.com|ingress-internal-b9753b17e64918a9.elb.eu-west-1.amazonaws.com>
Ingress Class:    nginx-internal
Default backend:  <default>
Rules:
  Host                                       Path  Backends
  ----                                       ----  --------
  <http://stg-1.apps.vwg.rivianvw.io|stg-1.apps.vwg.rivianvw.io>
                                             /   oathkeeper-svc:4455 (100.64.113.122:4455)
  <http://web.stg-1.apps.vwg.rivianvw.io|web.stg-1.apps.vwg.rivianvw.io>
                                             /   oathkeeper-svc:4455 (100.64.113.122:4455)
  <http://docs.stg-1.apps.vwg.rivianvw.io|docs.stg-1.apps.vwg.rivianvw.io>
                                             /   oathkeeper-svc:4455 (100.64.113.122:4455)
  <http://lineside.stg-1.apps.vwg.rivianvw.io|lineside.stg-1.apps.vwg.rivianvw.io>
                                             /   oathkeeper-svc:4455 (100.64.113.122:4455)
  <http://lineside-admin.stg-1.apps.vwg.rivianvw.io|lineside-admin.stg-1.apps.vwg.rivianvw.io>
                                             /   oathkeeper-svc:4455 (100.64.113.122:4455)
  <http://ride.stg-1.apps.vwg.rivianvw.io|ride.stg-1.apps.vwg.rivianvw.io>
                                             /   oathkeeper-svc:4455 (100.64.113.122:4455)
  <http://elemental.stg-1.apps.vwg.rivianvw.io|elemental.stg-1.apps.vwg.rivianvw.io>
                                             /   oathkeeper-svc:4455 (100.64.113.122:4455)
  <http://login.stg-1.apps.vwg.rivianvw.io|login.stg-1.apps.vwg.rivianvw.io>
                                             /   login-svc:80 (100.64.113.116:80)
  <http://kratos.stg-1.apps.vwg.rivianvw.io|kratos.stg-1.apps.vwg.rivianvw.io>
                                             /   oathkeeper-svc:4455 (100.64.113.122:4455)
  <http://frame.stg-1.apps.vwg.rivianvw.io|frame.stg-1.apps.vwg.rivianvw.io>
                                             /   oathkeeper-svc:4455 (100.64.113.122:4455)
  <http://middleware.stg-1.apps.vwg.rivianvw.io|middleware.stg-1.apps.vwg.rivianvw.io>
                                             /   oathkeeper-svc:4455 (100.64.113.122:4455)
  <http://keto.stg-1.apps.vwg.rivianvw.io|keto.stg-1.apps.vwg.rivianvw.io>
                                             /   keto-svc:4466 (100.64.115.0:4466)
  <http://hydra.stg-1.apps.vwg.rivianvw.io|hydra.stg-1.apps.vwg.rivianvw.io>
                                             /   hydra-svc:4444 (100.64.115.2:4444)
Annotations:                                 <http://argocd.argoproj.io/tracking-id|argocd.argoproj.io/tracking-id>: auth-c0-vwg-stg-1-ew1:<http://networking.k8s.io/Ingress:auth-c0/internal|networking.k8s.io/Ingress:auth-c0/internal>
Events:                                      <none>
b
A message from kapa.ai
Based on your provided
kubectl describe ingress
output and the available knowledge sources, your setup appears to follow a common pattern for deploying Ory Oathkeeper in Kubernetes: - Oathkeeper is deployed as a service (
oathkeeper-svc:4455
)
and is set as the backend for most of your application hostnames. This means all incoming requests to those hosts will be routed through Oathkeeper, which can then enforce your access rules and authentication/authorization logic before proxying to your upstream services. - Specialized services like
login-svc
,
keto-svc
, and
hydra-svc
are routed directly, which is a typical approach if you want to expose those services separately or if they have their own access control mechanisms. This matches the general deployment pattern described in the knowledge sources, where Oathkeeper acts as a reverse proxy in front of your services, and is deployed as a container (or service) in your Kubernetes cluster. The Ingress is configured to forward requests to Oathkeeper, which then applies its access rules and proxies allowed requests to the appropriate upstream service within your cluster. Configuration and access rules are typically mounted via ConfigMaps or Secrets, and health checks can be configured using Kubernetes probes[GitHub Issue: 600Mb of memory consumed on the start](https://github.com/ory/oathkeeper/issues/1230). What to check: - Ensure your Oathkeeper access rules are correctly configured to match the routes and methods you want to allow or deny. - Make sure your Oathkeeper service is healthy and reachable at the specified port. - If you want to protect the login, hydra, or keto endpoints with Oathkeeper as well, you would need to route those through Oathkeeper instead of directly to their services. Summary: Your setup looks correct for routing most application traffic through Oathkeeper using Kubernetes Ingress. This is a standard and recommended approach for enforcing centralized access control in a Kubernetes environment with Ory Oathkeeper. If you have specific issues or want to protect additional endpoints, you may need to adjust your Ingress or Oathkeeper rules accordingly.
b
@chilly-king-10285 - can I change the login display value of the button for an oidc provider .. example
Copy code
methods:
    oidc:
      config:
        providers:
          #- id: "RVW Tech"
          - id: microsoft
Shows up as
microsoft
on the login display button
b
A message from kapa.ai
Yes, you can change the display value of the login button for an OIDC provider by setting the
label
field in your OIDC provider configuration. The
id
field is used internally, while the
label
field controls what is shown to users on the login button. For example:
Copy code
yaml methods:   oidc:     config:       providers:         - id: microsoft           label: "Sign in with RVW Tech"           client_id: "..."           client_secret: "..."           provider: microsoft
With this configuration, the login button will display "Sign in with RVW Tech" instead of "microsoft" or the provider ID. This is supported in both the Ory Console and CLI as described in the [Ory social sign-in documentation](https://www.ory.sh/docs/identities/get-started/social-sign-in#add-a-social-provider). If you do not set the
label
, the button will default to showing the provider's
id
(e.g., "microsoft") as you observed.
b
@chilly-king-10285 - how can I create an API key so I can call the kratos and hydra endpoints
b
A message from kapa.ai
For self-hosted Ory Kratos and Hydra, the knowledge sources indicate that these open-source servers do not come with integrated API key management or access control by default. This means that, out of the box, all requests to their APIs are considered authenticated and authorized, and will be executed without any API key or token checks. This is a significant security consideration, and the documentation strongly recommends protecting your APIs using an API gateway such as [Ory Oathkeeper](https://github.com/ory/oathkeeper) or a comparable solution to enforce authentication and authorization for API access [API access control](https://www.ory.sh/docs/self-hosted/operations/api-access-control). In summary: - There is no built-in way to create an API key for self-hosted Ory Kratos or Hydra. - You should use an API gateway (like Oathkeeper) to secure your endpoints and implement API key or token-based access control. - For Ory Network (the managed cloud offering), API keys can be created via the Ory Console for admin and project APIs, but this does not apply to self-hosted instances [Ory Network API Keys](https://www.ory.sh/docs/concepts/personal-access-token). If you need to secure your self-hosted Ory APIs, you must implement access control externally, for example, by configuring Oathkeeper to require an API key or other authentication method for requests to Kratos and Hydra. If you need more details on how to set up Oathkeeper for this purpose, let me know!
b
@chilly-king-10285 - how can I have a local service calling a self hosted instance of kratos. how can I create a session cookie for local testing to attach to my calls.
b
A message from kapa.ai
To have a local service call a self-hosted instance of Ory Kratos and attach a session cookie for local testing, you should follow these steps: 1. Start a local Kratos instance and ensure it is accessible (e.g., at
<http://localhost:4433>
for the public API). 2. Obtain a session cookie by performing a login or registration flow using the browser or programmatically. When a user successfully logs in or registers via the browser-based flow, Kratos issues a session cookie (typically named
ory_kratos_session
) [Session management overview](https://www.ory.sh/docs/kratos/session-management/overview#using-ory-session-cookie). 3. Extract the session cookie from your browser's developer tools (Application > Cookies) after a successful login or registration. 4. Attach the session cookie to your local service's HTTP requests to Kratos. For example, in Go or Node.js, you can set the
Cookie
header:
Copy code
go    // Go example using the Kratos Go SDK    session, _, err := apiClient.FrontendApi.ToSession(context.Background()).        Cookie("ory_kratos_session=<your-session-cookie-here>").Execute()
Or with cURL:
Copy code
sh    curl '<http://localhost:4433/sessions/whoami>' \      -H 'Cookie: ory_kratos_session=<your-session-cookie-here>'
This allows your local service to authenticate as the user associated with that session cookie [Self-hosted Go example](https://www.ory.sh/docs/kratos/self-hosted/go) | [Session management overview](https://www.ory.sh/docs/kratos/session-management/overview#using-ory-session-cookie). Note: - The session cookie is only valid for the domain and path it was issued for. For local development, ensure your service and Kratos are both using
localhost
(not
127.0.0.1
), as cookies are domain-specific [Local development](https://www.ory.sh/docs/getting-started/local-development#local-development). - If you need to automate login for testing, you can use the Kratos SDK to programmatically perform the login flow and extract the session cookie from the response. This approach allows your local service to make authenticated calls to your self-hosted Kratos instance using a valid session cookie.
b
@chilly-king-10285 can i create an authorizer that white lists a set of email addresses
b
A message from kapa.ai
Yes, you can create an authorizer in Ory Oathkeeper that whitelists a set of email addresses. The best approach is to use the
remote
authorizer, which allows you to implement custom authorization logic. ## Using the
remote
Authorizer for Email Whitelisting The
remote
authorizer in Oathkeeper makes an HTTP request to a service you control, which can implement your email whitelist logic. Here's how to set it up:
Copy code
json {   "id": "some-id",   "upstream": {     "url": "<http://my-backend-service>"   },   "match": {     "url": "<http://my-app/api/<.*>>",     "methods": ["GET"]   },   "authenticators": [     {       "handler": "bearer_token"     }   ],   "authorizer": {     "handler": "remote",     "config": {       "remote": "<http://my-email-authorizer/authorize>",       "headers": {         "X-Subject": "{{ print .Subject }}",         "X-Email": "{{ print .Extra.email }}"       }     }   },   "mutators": [     {       "handler": "noop"     }   ] }
In this configuration: 1. The
remote
authorizer sends a request to your custom authorization service 2. It passes the user's email address in the
X-Email
header (assuming it's available in the
.Extra.email
field) 3. Your service checks if the email is in the whitelist and returns: - HTTP 200 OK if the email is allowed - HTTP 403 Forbidden if the email is not allowed [Oathkeeper Pipeline Authorizers](https://www.ory.sh/docs/oathkeeper/pipeline/authz#remote) explains that the remote authorizer makes a HTTP POST request to your endpoint, and access is allowed only if your service returns a "200 OK" response. ## Implementing the Email Whitelist Service Your email whitelist service could be a simple HTTP server that: 1. Receives the request from Oathkeeper 2. Extracts the email from the headers 3. Checks if the email is in your whitelist 4. Returns the appropriate status code This approach gives you complete flexibility to implement any authorization logic you need, including email whitelisting, role-based access control, or more complex rules. Would you like more details on implementing the email whitelist service or configuring Oathkeeper for this use case?
b
@chilly-king-10285 - can i set the email addresses statically in the configuration like this
Copy code
"authorizers": [
    {
      "handler": "allow",
      "config": {
        "subject_matches": [
          "<mailto:user1@example.com|user1@example.com>",
          "<mailto:admin@example.com|admin@example.com>",
          "<mailto:another.user@goriv.co|another.user@goriv.co>"
        ]
      }
    }
  ],
b
A message from kapa.ai
Based on the knowledge sources available, there isn't a direct configuration option in Ory Oathkeeper's
allow
authorizer that supports a
subject_matches
field with a list of email addresses as shown in your example. The
allow
authorizer in Oathkeeper is a simple authorizer that permits every action without any configuration options. As described in the [Oathkeeper Pipeline Authorizers documentation](https://www.ory.sh/docs/oathkeeper/pipeline/authz): > This authorizer permits every action allowed. > > ####
allow
configuration > > This handler isn't configurable. For email address whitelisting, you have a few alternative approaches: 1. Use the
remote_json
authorizer
: This allows you to send a JSON payload to a remote service that can check if the subject (email) is in your whitelist. 2. Use the
remote
authorizer
: Similar to
remote_json
, but it forwards the original request body. 3. Create a custom authorizer: If you're running a self-hosted instance, you could potentially develop a custom authorizer plugin. If you want to implement a static whitelist without an external service, you might need to use Ory Keto (Permissions) with the
keto_engine_acp_ory
authorizer, where you can define policies that include specific subjects. For a simpler approach, you could create a small microservice that implements the whitelist logic and use the
remote
authorizer to call it, as I suggested in our previous conversation. Would you like me to provide more details on implementing one of these alternative approaches for your email whitelist?
b
@chilly-king-10285 - can i convert a session_cookie to an id token in oathkeeper and if so how
b
A message from kapa.ai
Yes, you can convert a session cookie to an ID token (JWT) in Ory Oathkeeper by using the
id_token
mutator. This is a common pattern for enabling APIs or services that require JWTs for authentication, even if the original authentication was performed using a session cookie (such as the one issued by Ory Kratos). How it works: 1. Authenticate the session cookie: Use the
cookie_session
authenticator in your Oathkeeper access rule to validate the Ory Kratos session cookie. 2. Mutate to an ID token: Use the
id_token
mutator in your access rule. This mutator takes the authentication information (such as the subject and extra session data) and transforms it into a signed JWT (ID token). The JWT is then added to the
Authorization: Bearer <jwt>
header of the proxied request. Example Oathkeeper Access Rule:
Copy code
json {   "id": "example-rule",   "upstream": {     "url": "<http://my-backend-service>"   },   "match": {     "url": "<http://my-app/api/.*>",     "methods": ["GET", "POST"]   },   "authenticators": [     {       "handler": "cookie_session",       "config": {         "check_session_url": "<http://kratos:4433/sessions/whoami>",         "only": ["ory_kratos_session"]       }     }   ],   "authorizer": {     "handler": "allow"   },   "mutators": [     {       "handler": "id_token",       "config": {         "issuer_url": "<http://oathkeeper:4455/>",         "jwks_url": "file:///etc/config/oathkeeper/id_token.jwks.json",         "claims": "{\"session\": {{ .Extra | toJson }}}"       }     }   ] }
- The
cookie_session
authenticator validates the session cookie. - The
id_token
mutator issues a JWT containing session data. - The JWT is signed using the key specified in
jwks_url
and is available to your backend via the
Authorization
header. References: - [Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token) - [Zero Trust with Access Proxy guide](https://www.ory.sh/docs/kratos/guides/zero-trust-iap-proxy-identity-access-proxy#id-token-mutator) - [Oathkeeper Mutator id_token configuration](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token-configuration) This setup allows you to seamlessly convert a session cookie to an ID token (JWT) for downstream services that require JWT-based authentication.
b
@chilly-king-10285 - what does the configuration look like for this to work
b
A message from kapa.ai
I'm sorry, but the knowledge sources provided do not contain the configuration details or example values for the public Ory Oathkeeper Helm chart. While there are references to running Oathkeeper in Kubernetes and general best practices for mounting configuration and rules via ConfigMaps or Secrets, there is no explicit information or sample configuration for the public Helm chart in the sources above. If you need this information, please refer to the official Ory Helm charts documentation or the Ory GitHub repository.
A message from kapa.ai
I'm sorry, but the knowledge sources do not contain a complete example or explicit configuration for the public Ory Oathkeeper Helm chart, nor do they provide a full sample of the required values or configuration files for deploying Oathkeeper via Helm. The sources do describe how Oathkeeper is typically run in Kubernetes, with configuration and access rules mounted via ConfigMaps or Secrets, and how to set up access rules and mutators for session cookie to ID token conversion, but they do not provide a full Helm chart configuration or values file example for this setup [GitHub Issue: 600Mb of memory consumed on the start](https://github.com/ory/oathkeeper/issues/1230). If you need the exact Helm chart configuration, you will need to consult the official Ory Helm charts documentation or the Ory GitHub repository, as this information is not present in the provided knowledge sources.
b
@chilly-king-10285 what is happening with the logs for oathkeeper ime=2025-06-27T060007Z level=info msg=started handling request http_request=map[headersmap[accepttext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9 cache-control:no-cache cookie:Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". pragma:no-cache referer:https://login.stg-1.apps.vwg.rivianvw.io/ sec-ch-ua:"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:document sec-fetch-mode:navigate sec-fetch-site:same-site sec-fetch-user:?1 upgrade-insecure-requests:1 user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 x-forwarded-for:10.223.99.34 x-forwarded-host:frame.stg-1.apps.vwg.rivianvw.io x-forwarded-port:80 x-forwarded-proto:http x-forwarded-scheme:http x-real-ip:10.223.99.34 x-request-id:f8cfdd54b2d403f5067ba3face9aa25f x-scheme:http] host:frame.stg-1.apps.vwg.rivianvw.io method:GET path:/ query:<nil> remote100.64.47.22453040 scheme:http] time=2025-06-27T060008Z level=warning msg=The mutation handler encountered an error audience=application error=map[message:error executing headers template "Bearer {{ print .IDToken }}" in rule "rvtechframeprotected": template: rvtechframeprotectedAuthorization116 executing "rvtechframeprotected:Authorization" at <.IDToken>: can't evaluate field IDToken in type *authn.AuthenticationSession] granted=false http_host=frame.stg-1.apps.vwg.rivianvw.io http_method=GET http_url=http://frame.stg-1.apps.vwg.rivianvw.io/ http_user_agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 mutation_handler=header reason_id=mutation_handler_error rule_id=rvtechframeprotected service_name=ORY Oathkeeper service_version=v0.40.9 subject=14d761e8-d5bd-42d3-b940-a4b4b255f65a time=2025-06-27T060008Z level=warning msg=Access request denied audience=application error=map[message:error executing headers template "Bearer {{ print .IDToken }}" in rule "rvtechframeprotected": template: rvtechframeprotectedAuthorization116 executing "rvtechframeprotected:Authorization" at <.IDToken>: can't evaluate field IDToken in type *authn.AuthenticationSession] granted=false http_host=frame.stg-1.apps.vwg.rivianvw.io http_method=GET http_url=http://frame.stg-1.apps.vwg.rivianvw.io/ http_user_agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 service_name=ORY Oathkeeper service_version=v0.40.9 time=2025-06-27T060008Z level=error msg=An error occurred while handling a request audience=application error=map[debug: message:An internal server error occurred, please contact the system administrator reason: status:Internal Server Error status_code:500] http_request=map[headersmap[accepttext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9 cache-control:no-cache cookie:Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". pragma:no-cache referer:https://login.stg-1.apps.vwg.rivianvw.io/ sec-ch-ua:"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:document sec-fetch-mode:navigate sec-fetch-site:same-site sec-fetch-user:?1 upgrade-insecure-requests:1 user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 x-forwarded-port:80 x-forwarded-scheme:http x-real-ip:10.223.99.34 x-request-id:f8cfdd54b2d403f5067ba3face9aa25f x-scheme:http] host:frame.stg-1.apps.vwg.rivianvw.io method:GET path:/ query:<nil> remote100.64.47.22453040 scheme:http] http_response=map[status_code:500] service_name=ORY Oathkeeper service_version=v0.40.9 time=2025-06-27T060008Z level=info msg=completed handling request http_request=map[headersmap[accepttext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9 cache-control:no-cache cookie:Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". pragma:no-cache referer:https://login.stg-1.apps.vwg.rivianvw.io/ sec-ch-ua:"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:document sec-fetch-mode:navigate sec-fetch-site:same-site sec-fetch-user:?1 upgrade-insecure-requests:1 user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 x-forwarded-for:10.223.99.34 x-forwarded-host:frame.stg-1.apps.vwg.rivianvw.io x-forwarded-port:80 x-forwarded-proto:http x-forwarded-scheme:http x-real-ip:10.223.99.34 x-request-id:f8cfdd54b2d403f5067ba3face9aa25f x-scheme:http] host:frame.stg-1.apps.vwg.rivianvw.io method:GET path:/ query:<nil> remote100.64.47.22453040 scheme:http] http_response=map[headersmap[content typeapplication/json] size:188 status:500 text_status:Internal Server Error took:78.579368ms] time=2025-06-27T060008Z level=info msg=started handling request http_request=map[headersmap[acceptimage/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9 cache-control:no-cache cookie:Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". pragma:no-cache referer:https://frame.stg-1.apps.vwg.rivianvw.io/ sec-ch-ua:"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:image sec-fetch-mode:no-cors sec-fetch-site:same-origin user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 x-forwarded-for:10.223.99.34 x-forwarded-host:frame.stg-1.apps.vwg.rivianvw.io x-forwarded-port:80 x-forwarded-proto:http x-forwarded-scheme:http x-real-ip:10.223.99.34 x-request-id:d0eeeb15f6fbd2fa620d6b349d2c2f87 x-scheme:http] host:frame.stg-1.apps.vwg.rivianvw.io method:GET path:/favicon.ico query:<nil> remote100.64.47.22453040 scheme:http] time=2025-06-27T060008Z level=warning msg=The mutation handler encountered an error audience=application error=map[message:error executing headers template "Bearer {{ print .IDToken }}" in rule "rvtechframeprotected": template: rvtechframeprotectedAuthorization116 executing "rvtechframeprotected:Authorization" at <.IDToken>: can't evaluate field IDToken in type *authn.AuthenticationSession] granted=false http_host=frame.stg-1.apps.vwg.rivianvw.io http_method=GET http_url=http://frame.stg-1.apps.vwg.rivianvw.io/favicon.ico http_user_agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 mutation_handler=header reason_id=mutation_handler_error rule_id=rvtechframeprotected service_name=ORY Oathkeeper service_version=v0.40.9 subject=14d761e8-d5bd-42d3-b940-a4b4b255f65a time=2025-06-27T060008Z level=warning msg=Access request denied audience=application error=map[message:error executing headers template "Bearer {{ print .IDToken }}" in rule "rvtechframeprotected": template: rvtechframeprotectedAuthorization116 executing "rvtechframeprotected:Authorization" at <.IDToken>: can't evaluate field IDToken in type *authn.AuthenticationSession] granted=false http_host=frame.stg-1.apps.vwg.rivianvw.io http_method=GET http_url=http://frame.stg-1.apps.vwg.rivianvw.io/favicon.ico http_user_agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 service_name=ORY Oathkeeper service_version=v0.40.9 time=2025-06-27T060008Z level=error msg=An error occurred while handling a request audience=application error=map[debug: message:An internal server error occurred, please contact the system administrator reason: status:Internal Server Error status_code:500] http_request=map[headersmap[acceptimage/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9 cache-control:no-cache cookie:Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". pragma:no-cache referer:https://frame.stg-1.apps.vwg.rivianvw.io/ sec-ch-ua:"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:image sec-fetch-mode:no-cors sec-fetch-site:same-origin user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 x-forwarded-port:80 x-forwarded-scheme:http x-real-ip:10.223.99.34 x-request-id:d0eeeb15f6fbd2fa620d6b349d2c2f87 x-scheme:http] host:frame.stg-1.apps.vwg.rivianvw.io method:GET path:/favicon.ico query:<nil> remote100.64.47.22453040 scheme:http] http_response=map[status_code:500] service_name=ORY Oathkeeper service_version=v0.40.9 time=2025-06-27T060008Z level=info msg=completed handling request http_request=map[headersmap[acceptimage/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9 cache-control:no-cache cookie:Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". pragma:no-cache referer:https://frame.stg-1.apps.vwg.rivianvw.io/ sec-ch-ua:"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:image sec-fetch-mode:no-cors sec-fetch-site:same-origin user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 x-forwarded-for:10.223.99.34 x-forwarded-host:frame.stg-1.apps.vwg.rivianvw.io x-forwarded-port:80 x-forwarded-proto:http x-forwarded-scheme:http x-real-ip:10.223.99.34 x-request-id:d0eeeb15f6fbd2fa620d6b349d2c2f87 x-scheme:http] host:frame.stg-1.apps.vwg.rivianvw.io method:GET path:/favicon.ico query:<nil> remote100.64.47.22453040 scheme:http] http_response=map[headersmap[content typeapplication/json] size:188 status:500 text_status:Internal Server Error took:18.040944ms]
b
A message from kapa.ai
Your Oathkeeper logs show that requests are being denied due to an error in the mutation (mutator) handler, specifically in the header mutator. The error message is:
Copy code
error executing headers template "Bearer {{ print .IDToken }}" in rule "rvtech:frame:protected": template: rvtech:frame:protected:Authorization:1:16: executing "rvtech:frame:protected:Authorization" at <.IDToken>: can't evaluate field IDToken in type *authn.AuthenticationSession
What this means: - The header mutator is trying to use the template variable
{{ print .IDToken }}
. - However,
.IDToken
does not exist in the
AuthenticationSession
struct that is passed to the mutator. As a result, the template engine cannot evaluate this field, causing the mutation handler to fail and the request to be denied with a 500 Internal Server Error. Why this happens: - In Oathkeeper, the available fields for templating in mutators are those present in the
AuthenticationSession
struct, such as
.Subject
,
.Extra
,
.Header
, and
.MatchContext
([Session variables documentation](https://www.ory.sh/docs/oathkeeper/pipeline#session)). -
.IDToken
is not a standard field in this struct. If you want to inject an ID token into a header, you must first use the
id_token
mutator, which will generate the token and make it available for further mutation or as a header. How to fix: - If you want to add a Bearer token to the
Authorization
header, use the
id_token
mutator, which will generate a JWT and set it as the
Authorization
header automatically. - Alternatively, if you want to use a header mutator, you need to reference fields that actually exist in the session, such as
{{ print .Subject }}
or
{{ print .Extra.some_field }}
. Summary: The error is due to referencing a non-existent
.IDToken
field in your header mutator template. Update your rule to use valid fields from the
AuthenticationSession
struct, or use the
id_token
mutator if you want to generate and inject a JWT ([Oathkeeper Mutators documentation](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token), [Session variables](https://www.ory.sh/docs/oathkeeper/pipeline#session)).
b
@chilly-king-10285 here is my oathkeeper route configuration.. { "id": "rvtechframeprotected", "upstream": { "url": "http://frame-svc" }, "match": { "url": "<http|https>:<//frame.stg-1.apps.vwg.rivianvw.io/><(.*)?$>", "methods": ["GET", "POST", "OPTIONS"] }, "authenticators": [ { "handler": "cookie_session", "config": { "check_session_url": "http://kratos-public:4433/sessions/whoami", "only": ["ory_kratos_session"] } } ], "authorizer": { "handler": "allow" }, "mutators": [ { "handler": "id_token", "config": { "issuer_url": "https://oathkeeper-proxy.stg-1.apps.vwg.rivianvw.io", "jwks_url": "file:///etc/secrets/mutator.id_token.jwks.json", "claims": "{\"session\": {{ .Extra | toJson }}}" } }, { "handler": "header", "config": { "headers": { "X-User-Subject": "{{ print .Subject }}", "Authorization": "Bearer {{ print .IDToken }}" } } } ] }
b
A message from kapa.ai
Your Oathkeeper route configuration is almost correct for converting a session cookie to an ID token and forwarding it as a Bearer token in the
Authorization
header. However, the issue is with the use of
{{ print .IDToken }}
in your header mutator. Problem: The field
.IDToken
does not exist in the
AuthenticationSession
struct that Oathkeeper exposes to mutators. This is confirmed by the error in your logs:
Copy code
can't evaluate field IDToken in type *authn.AuthenticationSession
How the
id_token
mutator works:
The
id_token
mutator generates a JWT and, by default, sets it as the
Authorization: Bearer <jwt>
header for the upstream request. You do not need to use a header mutator to set the
Authorization
header with the ID token; the
id_token
mutator does this automatically ([Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token)). What you should do: - Remove the
Authorization
header from your header mutator.
- If you want to add custom headers (like
X-User-Subject
), you can keep those in the header mutator. Corrected configuration:
Copy code
json {   "id": "rvtech:frame:protected",   "upstream": {     "url": "<http://frame-svc>"   },   "match": {     "url": "<http|https>://frame.stg-1.apps.vwg.rivianvw.io/<(.*)?$>",     "methods": ["GET", "POST", "OPTIONS"]   },   "authenticators": [     {       "handler": "cookie_session",       "config": {         "check_session_url": "<http://kratos-public:4433/sessions/whoami>",         "only": ["ory_kratos_session"]       }     }   ],   "authorizer": { "handler": "allow" },   "mutators": [     {       "handler": "id_token",       "config": {         "issuer_url": "<https://oathkeeper-proxy.stg-1.apps.vwg.rivianvw.io>",         "jwks_url": "file:///etc/secrets/mutator.id_token.jwks.json",         "claims": "{\"session\": {{ .Extra | toJson }}}"       }     },     {       "handler": "header",       "config": {         "headers": {           "X-User-Subject": "{{ print .Subject }}"         }       }     }   ] }
Summary: - The
id_token
mutator will generate and inject the JWT as the
Authorization: Bearer <jwt>
header. - Do not reference
.IDToken
in the header mutator, as it does not exist. - You can use the header mutator for other custom headers, such as
X-User-Subject
. This approach matches the documented and supported usage in the Ory Oathkeeper knowledge sources and will resolve the error you are seeing in your logs.
b
@chilly-king-10285 - I am not seeing the bearer token being attached to the header. here is my config.yaml
Copy code
access_rules:
  repositories:
  - file:///etc/rules/access-rules.json
authenticators:
  anonymous:
    config:
      subject: guest
    enabled: true
  cookie_session:
    config:
      check_session_url: <http://kratos-public:4433/sessions/whoami>
      extra_from: '@this'
      preserve_path: true
      preserve_query: true
      subject_from: identity.id
    enabled: true
  noop:
    enabled: true
authorizers:
  allow:
    enabled: true
  deny:
    enabled: true
  keto_engine_acp_ory:
    config:
      base_url: <https://keto-instance.com>
      required_action: ""
      required_resource: ""
    enabled: false
errors:
  fallback:
  - json
  handlers:
    json:
      config:
        verbose: false
      enabled: true
    redirect:
      config:
        to: <https://kratos.stg-1.apps.vwg.rivianvw.io/self-service/login/browser?return_to=%s>
        when:
        - error:
          - unauthorized
          - forbidden
          request:
            header:
              accept:
              - text/html
      enabled: true
log:
  format: json
  leak_sensitive_values: true
  level: debug
  redaction_text: ""
mutators:
  header:
    config:
      headers:
        X-User-Subject: '{{ print .Subject }}'
    enabled: true
  id_token:
    config:
      claims: |
        {
          "sub": "{{ .Subject }}",
          "email": "{{ .Extra.identity.traits.email }}",
          "name": {
            "first": "{{ .Extra.identity.traits.name.first }}",
            "last": "{{ .Extra.identity.traits.name.last }}"
          }
        }
      issuer_url: <https://oathkeeper-proxy.stg-1.apps.vwg.rivianvw.io>
      jwks_url: file:///etc/secrets/mutator.id_token.jwks.json
      ttl: 15m
    enabled: true
  noop:
    enabled: true
serve:
  api:
    port: 4456
  prometheus:
    port: 9000
  proxy:
    port: 4455
~ $
Copy code
{
    "id": "rvtech:frame:protected",
    "upstream": {
      "url": "<http://frame-svc>"
    },
    "match": {
      "url": "<http|https>://frame.stg-1.apps.vwg.rivianvw.io/<(.*)?$>",
      "methods": ["GET", "POST", "OPTIONS"]
    },
    "authenticators": [
        {
          "handler": "cookie_session",
          "config": {
            "check_session_url": "<http://kratos-public:4433/sessions/whoami>",
            "only": ["ory_kratos_session"]
          }
        }
    ],
    "authorizer": { "handler": "allow" },
    "mutators": [
        {
          "handler": "id_token",
          "config": {
            "issuer_url": "<https://oathkeeper-proxy.stg-1.apps.vwg.rivianvw.io>",
            "jwks_url": "file:///etc/secrets/mutator.id_token.jwks.json",
            "claims": "{\"session\": {{ .Extra | toJson }}}"
          }
        },
        {
          "handler": "header",
          "config": {
            "headers": {
              "X-User-Subject": "{{ print .Subject }}",
            }
          }
        }
      ]
  }
b
A message from kapa.ai
Based on your configuration and the knowledge sources, the
id_token
mutator in Ory Oathkeeper is responsible for generating a JWT and, by default, setting it as the
Authorization: Bearer <jwt>
header for the proxied request. You do not need to use the header mutator to set the
Authorization
header with the ID token; the
id_token
mutator does this automatically. The header mutator is only needed if you want to add additional custom headers (like
X-User-Subject
) [Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token). Why you might not see the Bearer token: - If the
id_token
mutator is present and enabled in your rule, and the mutator is correctly configured, Oathkeeper will inject the JWT as the
Authorization
header. - If you are not seeing the
Authorization: Bearer <jwt>
header, possible reasons include: - The
id_token
mutator is not being executed (e.g., due to rule matching issues or an error in the mutator). - There is an error in the mutator configuration or in the JWKS file. - The upstream service or a proxy in front of Oathkeeper is stripping or not forwarding the
Authorization
header. What to check: 1. Logs: Check Oathkeeper logs for any errors related to the
id_token
mutator. Errors in the mutator will prevent the header from being set. 2. Rule Matching: Ensure the request matches the rule that includes the
id_token
mutator. 3. JWKS File: Make sure the JWKS file is present and readable at the specified path. 4. Header Forwarding: Ensure there is no proxy or middleware between Oathkeeper and your upstream service that strips the
Authorization
header. Summary: With your current configuration, the
id_token
mutator should automatically set the
Authorization: Bearer <jwt>
header. You do not need to set this header manually in the header mutator. If the header is missing, check for errors in the mutator, rule matching, or header forwarding as described above [Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token).
b
@chilly-king-10285 - I'm receiving a CORS error when I POST to an endpoint.. {"audience":"application","error":{"debug":"","message":"Requested url does not match any rules","reason":"","stack_trace":"\ngithub.com/ory/oathkeeper/rule.(*RepositoryMemory).Match\n\t/project/rule/repository_memory.go:140\ngithub.com/ory/oathkeeper/proxy.(*Proxy).Rewrite\n\t/project/proxy/proxy.go:126\nnet/http/httputil.(*ReverseProxy).ServeHTTP\n\t/usr/local/go/src/net/http/httputil/reverseproxy.go:433\ngithub.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/cmd/server.runProxy.func1.ContextualizedMiddleware.6\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/corsx/middleware.go:28\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/reqlog.(*Middleware).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/reqlog/middleware.go:143\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/metrics.(*Middleware).ServeHTTP\n\t/project/metrics/middleware.go:105\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/metricsx.(*Service).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/metricsx/middleware.go:272\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/urfave/negroni.(*Negroni).ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:96\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.(*middleware).serveHTTP\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go:176\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.NewMiddleware.func1.1\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go65\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go2220\nnet/http.serverHandler.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:3210\nnet/http.(*conn).serve\n\t/usr/local/go/src/net/http/server.go2092\nruntime.goexit\n\t/usr/local/go/src/runtime/asm amd64.s1700","status":"Not Found","status_code":404},"granted":false,"http_host":"middleware.stg-1.apps.vwg.rivianvw.io","http_method":"OPTIONS","http_url":"http://middleware.stg-1.apps.vwg.rivianvw.io/api/oauth-clients","http_user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36","level":"warning","msg":"Access request denied","service_name":"ORY Oathkeeper","service_version":"v0.40.9","time":"2025-07-01T212043.297837605Z"} {"audience":"application","error":{"debug":"","message":"The requested resource could not be found","reason":"","stack_trace":"\ngithub.com/ory/oathkeeper/rule.(*RepositoryMemory).Match\n\t/project/rule/repository_memory.go:140\ngithub.com/ory/oathkeeper/proxy.(*Proxy).Rewrite\n\t/project/proxy/proxy.go:126\nnet/http/httputil.(*ReverseProxy).ServeHTTP\n\t/usr/local/go/src/net/http/httputil/reverseproxy.go:433\ngithub.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/cmd/server.runProxy.func1.ContextualizedMiddleware.6\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/corsx/middleware.go:28\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/reqlog.(*Middleware).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/reqlog/middleware.go:143\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/metrics.(*Middleware).ServeHTTP\n\t/project/metrics/middleware.go:105\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/metricsx.(*Service).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/metricsx/middleware.go:272\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/urfave/negroni.(*Negroni).ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:96\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.(*middleware).serveHTTP\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go:176\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.NewMiddleware.func1.1\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go65\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go2220\nnet/http.serverHandler.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:3210\nnet/http.(*conn).serve\n\t/usr/local/go/src/net/http/server.go2092\nruntime.goexit\n\t/usr/local/go/src/runtime/asm amd64.s1700","status":"Not Found","status_code":404},"http_request":{"headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","access-control-request-headers":"content-type","access-control-request-method":"POST","cache-control":"no-cache","origin":"https://web.stg-1.apps.vwg.rivianvw.io","pragma":"no-cache","referer":"https://web.stg-1.apps.vwg.rivianvw.io/","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"same-site","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36","x-forwarded-port":"80","x-forwarded-scheme":"http","x-real-ip":"10.223.98.123","x-request-id":"4a2033386d437a569030f0e629088935","x-scheme":"http"},"host":"middleware.stg-1.apps.vwg.rivianvw.io","method":"OPTIONS","path":"/api/oauth-clients","query":null,"remote":"100.64.48.210:44160","scheme":"http"},"http_response":{"status_code":404},"level":"info","msg":"An error occurred while handling a request","service_name":"ORY Oathkeeper","service_version":"v0.40.9","time":"2025-07-01T212043.298058399Z"}
b
A message from kapa.ai
Your logs and error message indicate that Oathkeeper is returning a 404 with the message: > "Requested url does not match any rules" This means that the incoming request (in this case, an
OPTIONS
preflight request for CORS) does not match any of the access rules you have configured in Oathkeeper. Key points from the logs: - The request is an
OPTIONS
request to
/api/oauth-clients
on the host
<http://middleware.stg-1.apps.vwg.rivianvw.io|middleware.stg-1.apps.vwg.rivianvw.io>
. - Oathkeeper could not find a rule that matches this combination of host, path, and method, so it returns a 404. Why this causes a CORS error: - Browsers send an
OPTIONS
preflight request before certain cross-origin requests (like
POST
with custom headers). - If Oathkeeper does not have a rule that matches the
OPTIONS
request, it will return a 404, and the browser will block the actual request due to failed CORS negotiation. How to fix: - Ensure you have an Oathkeeper rule that matches the
OPTIONS
method for the path and host in question. - Your rule should include
"methods": ["OPTIONS", "POST", ...]
and match the path
/api/oauth-clients
for the host
<http://middleware.stg-1.apps.vwg.rivianvw.io|middleware.stg-1.apps.vwg.rivianvw.io>
. Summary: Oathkeeper is denying the CORS preflight request because there is no matching rule for the
OPTIONS
method on the requested path and host. Add or update your access rule to explicitly allow the
OPTIONS
method for this endpoint to resolve the issue. This behavior is consistent with how Oathkeeper matches requests to rules, as seen in the error and stack trace in your logs.
b
@chilly-king-10285 how do I handle OPTIONS verbs in oathkeeper
b
A message from kapa.ai
To handle
OPTIONS
verbs (such as those used in CORS preflight requests) in Ory Oathkeeper, you must ensure that your access rules explicitly include the
OPTIONS
method in the
methods
array for the relevant paths and hosts. If an incoming
OPTIONS
request does not match any rule, Oathkeeper will return a 404 error, which will cause CORS preflight requests to fail and block the actual request from the browser. For example, your rule should look like this:
Copy code
json {   "id": "example-rule",   "upstream": {     "url": "<http://your-backend-service>"   },   "match": {     "url": "<http://your-host/api/your-endpoint>",     "methods": ["GET", "POST", "OPTIONS"]   },   "authenticators": [     { "handler": "noop" }   ],   "authorizer": { "handler": "allow" },   "mutators": [     { "handler": "noop" }   ] }
By including
"OPTIONS"
in the
methods
array, Oathkeeper will match and process preflight requests, allowing CORS to function correctly. This is necessary because, as shown in your logs, a missing rule for the
OPTIONS
method results in a "Requested url does not match any rules" error and a 404 response, which breaks CORS negotiation. This approach is confirmed by the error and behavior described in your logs and is consistent with Oathkeeper's rule matching logic [Oathkeeper API access rules](https://www.ory.sh/docs/oathkeeper/api-access-rules#access-rule-format).
c
@chilly-king-10285 - what if I have the cookie_session authenticator, since OPTIONS does not include cookies
b
A message from kapa.ai
If you have the
cookie_session
authenticator in your Oathkeeper rule, but the incoming request is an
OPTIONS
preflight (which does not include cookies), Oathkeeper will attempt to authenticate the request using the
cookie_session
authenticator. Since there is no cookie present, the authenticator will not be able to validate the session, and the request may be denied unless you have configured a fallback authenticator (such as
anonymous
or
noop
) in your rule. The knowledge sources confirm that Oathkeeper processes authenticators in order, and if the first authenticator cannot handle the request (e.g., due to missing cookies), it will pass the request to the next authenticator in the list. If none can handle the request, access is denied [Oathkeeper Authenticators](https://www.ory.sh/docs/oathkeeper/pipeline/authn). To handle CORS preflight (
OPTIONS
) requests when using `cookie_session`:
- Add a fallback authenticator like
anonymous
or
noop
after
cookie_session
in your rule's
authenticators
array. This allows unauthenticated
OPTIONS
requests to be accepted, while still requiring authentication for other methods. Example:
Copy code
json "authenticators": [   {     "handler": "cookie_session",     "config": {       "check_session_url": "<http://kratos-public:4433/sessions/whoami>",       "only": ["ory_kratos_session"]     }   },   { "handler": "anonymous" } ]
This setup ensures that authenticated requests use the session cookie, but unauthenticated
OPTIONS
requests (which lack cookies) are still allowed through, enabling proper CORS handling [Oathkeeper Authenticators](https://www.ory.sh/docs/oathkeeper/pipeline/authn). If you do not add a fallback authenticator, Oathkeeper will deny the
OPTIONS
request, resulting in CORS errors, as seen in your logs.
b
@chilly-king-10285 - a new error {"http_request":{"headers":{"accept":"*/*","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","access-control-request-headers":"content-type","access-control-request-method":"POST","cache-control":"no-cache","origin":"https://web.stg-1.apps.vwg.rivianvw.io","pragma":"no-cache","referer":"https://web.stg-1.apps.vwg.rivianvw.io/","sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"same-site","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36","x-forwarded-for":"10.223.101.238","x-forwarded-host":"middleware.stg-1.apps.vwg.rivianvw.io","x-forwarded-port":"80","x-forwarded-proto":"http","x-forwarded-scheme":"http","x-real-ip":"10.223.101.238","x-request-id":"9c299134275cae946d61e877f970b1a5","x-scheme":"http"},"host":"middleware.stg-1.apps.vwg.rivianvw.io","method":"OPTIONS","path":"/api/oauth-clients","query":null,"remote":"100.64.47.224:38910","scheme":"http"},"level":"info","msg":"started handling request","time":"2025-07-02T015010.239904265Z"} {"audience":"application","error":{"message":"error executing claims template in rule \"rvtechmiddlewareprotected\": template: c5d5ff9cc663bfa304739a1980f554d6145: executing \"c5d5ff9cc663bfa304739a1980f554d6\" at <.Extra.identity.traits.email>: nil pointer evaluating interface {}.traits","stack_trace":"\ngithub.com/ory/oathkeeper/pipeline/mutate.(*MutatorIDToken).Mutate\n\t/project/pipeline/mutate/mutator_id_token.go:152\ngithub.com/ory/oathkeeper/proxy.(*requestHandler).HandleRequest\n\t/project/proxy/request_handler.go:310\ngithub.com/ory/oathkeeper/proxy.(*Proxy).Rewrite\n\t/project/proxy/proxy.go:133\nnet/http/httputil.(*ReverseProxy).ServeHTTP\n\t/usr/local/go/src/net/http/httputil/reverseproxy.go:433\ngithub.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/cmd/server.runProxy.func1.ContextualizedMiddleware.6\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/corsx/middleware.go:28\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/reqlog.(*Middleware).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/reqlog/middleware.go:143\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/metrics.(*Middleware).ServeHTTP\n\t/project/metrics/middleware.go:105\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/metricsx.(*Service).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/metricsx/middleware.go:272\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/urfave/negroni.(*Negroni).ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:96\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.(*middleware).serveHTTP\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go:176\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.NewMiddleware.func1.1\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go65\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go2220\nnet/http.serverHandler.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:3210\nnet/http.(*conn).serve\n\t/usr/local/go/src/net/http/server.go2092\nruntime.goexit\n\t/usr/local/go/src/runtime/asm amd64.s1700"},"granted":false,"http_host":"middleware.stg-1.apps.vwg.rivianvw.io","http_method":"OPTIONS","http_url":"http://middleware.stg-1.apps.vwg.rivianvw.io/api/oauth-clients","http_user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36","level":"warning","msg":"The mutation handler encountered an error","mutation_handler":"id_token","reason_id":"mutation_handler_error","rule_id":"rvtechmiddlewareprotected","service_name":"ORY Oathkeeper","service_version":"v0.40.9","subject":"guest","time":"2025-07-02T015010.240880336Z"} {"audience":"application","error":{"message":"error executing claims template in rule \"rvtechmiddlewareprotected\": template: c5d5ff9cc663bfa304739a1980f554d6145: executing \"c5d5ff9cc663bfa304739a1980f554d6\" at <.Extra.identity.traits.email>: nil pointer evaluating interface {}.traits","stack_trace":"\ngithub.com/ory/oathkeeper/pipeline/mutate.(*MutatorIDToken).Mutate\n\t/project/pipeline/mutate/mutator_id_token.go:152\ngithub.com/ory/oathkeeper/proxy.(*requestHandler).HandleRequest\n\t/project/proxy/request_handler.go:310\ngithub.com/ory/oathkeeper/proxy.(*Proxy).Rewrite\n\t/project/proxy/proxy.go:133\nnet/http/httputil.(*ReverseProxy).ServeHTTP\n\t/usr/local/go/src/net/http/httputil/reverseproxy.go:433\ngithub.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/cmd/server.runProxy.func1.ContextualizedMiddleware.6\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/corsx/middleware.go:28\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/reqlog.(*Middleware).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/reqlog/middleware.go:143\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/metrics.(*Middleware).ServeHTTP\n\t/project/metrics/middleware.go:105\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/metricsx.(*Service).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/metricsx/middleware.go:272\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/urfave/negroni.(*Negroni).ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:96\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.(*middleware).serveHTTP\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go:176\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.NewMiddleware.func1.1\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go65\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go2220\nnet/http.serverHandler.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:3210\nnet/http.(*conn).serve\n\t/usr/local/go/src/net/http/server.go2092\nruntime.goexit\n\t/usr/local/go/src/runtime/asm amd64.s1700"},"granted":false,"http_host":"middleware.stg-1.apps.vwg.rivianvw.io","http_method":"OPTIONS","http_url":"http://middleware.stg-1.apps.vwg.rivianvw.io/api/oauth-clients","http_user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36","level":"warning","msg":"Access request denied","service_name":"ORY Oathkeeper","service_version":"v0.40.9","time":"2025-07-02T015010.240990168Z"}
b
A message from kapa.ai
Your new error is:
Copy code
error executing claims template in rule "rvtech:middleware:protected": template: ... at <.Extra.identity.traits.email>: nil pointer evaluating interface {}.traits
What’s happening: When an
OPTIONS
(CORS preflight) request is received, Oathkeeper is attempting to execute your
id_token
mutator’s claims template, which references
.Extra.identity.traits.email
. However, for unauthenticated or anonymous requests (like most preflight
OPTIONS
), there is no session, so
.Extra.identity
is
nil
. Attempting to access
.traits
on a
nil
value causes this error. Why this happens: - The
id_token
mutator is always run, even for unauthenticated requests (such as those handled by the
anonymous
authenticator). - The claims template expects session data that does not exist for anonymous or preflight requests. How to fix: You need to make your claims template resilient to missing data. The knowledge sources do not provide a direct example of conditional logic in claims templates, but the error and the Oathkeeper mutator documentation make it clear that you must avoid referencing fields that may not exist for all request types ([Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token)). Recommended approach: - Use the
anonymous
authenticator as a fallback for
OPTIONS
requests (which you are already doing). - Adjust your claims template to handle missing fields. For example, you can use the
default
function in Go templates to provide a fallback value:
Copy code
json "claims": "{   \"sub\": \"{{ .Subject }}\",   \"email\": \"{{ .Extra.identity.traits.email | default \"\" }}\" }"
This way, if
.Extra.identity.traits.email
is missing, it will default to an empty string, preventing the nil pointer error. Summary: Your error occurs because the claims template in your
id_token
mutator does not handle missing session data, which is common for unauthenticated requests like CORS preflights. Update your claims template to use default values for fields that may not exist to resolve this issue.
b
@chilly-king-10285 - this is my claims mapping.. for the issue above {{ if .Extra.identity }} { "sub": "{{ .Subject }}", "email": "{{ .Extra.identity.traits.email }}", {{ with .Extra.identity.traits.name }} {{ if kindIs "map" . }} "name": { {{ with index . "first" }} "first": "{{ . }}", {{ else }} "first": "", {{ end }} {{ with index . "last" }} "last": "{{ . }}" {{ else }} "last": "" {{ end }} } {{ else if kindIs "string" . }} "name": "{{ . }}" {{ else }} "name": { "first": "", "last": "" } {{ end }} {{ else }} "name": { "first": "", "last": "" } {{ end }} } {{ else }} {} {{ end }}
b
A message from kapa.ai
Your claims mapping uses Go template logic to conditionally include fields based on the presence and structure of
.Extra.identity
. This is the correct approach to avoid nil pointer errors when the identity or its traits are missing, which can happen for unauthenticated requests like CORS preflights. However, the knowledge sources do not provide explicit documentation or examples for using advanced Go templating (such as
kindIs
,
with
, or
index
) in Oathkeeper's
id_token
mutator claims. The official documentation only shows simple field access and the use of the
print
function for safe value extraction ([Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token), [Session variables](https://www.ory.sh/docs/oathkeeper/pipeline#session)). What is certain from the knowledge sources: - You must avoid referencing fields that may not exist for all request types, especially for unauthenticated or anonymous requests. - The
id_token
mutator will fail if your template tries to access a field on a
nil
value. - Using conditional logic to check for the existence of
.Extra.identity
before accessing its fields is the right strategy. What is uncertain: - The knowledge sources do not confirm that advanced Go template functions like
kindIs
or
index
are available in Oathkeeper's templating environment. Only the
print
and
printIndex
functions are explicitly documented. Recommendation: - If your current mapping works and avoids errors, it is a valid workaround. - If you encounter issues with unsupported template functions, simplify your template to only use documented functions and basic conditional checks, or use the
print
function to safely extract values. Summary: Your approach to conditionally include claims based on the presence of
.Extra.identity
is correct and aligns with best practices described in the knowledge sources. However, the use of advanced Go template functions is not explicitly documented as supported in Oathkeeper, so proceed with caution and test thoroughly. If you encounter issues, fall back to simpler, documented template constructs.
b
@chilly-king-10285 these are my access rules.. [ { "id": "rvtechkratospublic", "upstream": { "url": "http://kratos-svc:4433", "preserve_host": true, "strip_path": "/.ory/kratos/public" }, "match": { "url": "https//kratos.stg 1.apps.vwg.rivianvw.io4455/.ory/kratos/public/(.*)?$", "methods": ["GET", "POST", "PUT", "DELETE", "PATCH"] }, "authenticators": [{ "handler": "noop" }], "authorizer": { "handler": "allow" }, "mutators": [{ "handler": "noop" }] }, { "id": "rvtechloginprotected", "upstream": { "url": "http://login-svc" }, "match": { "url": "https://synapse-login.stg-1.apps.vwg.rivianvw.io/(.*)?$", "methods": ["GET", "POST"] }, "authenticators": [{ "handler": "noop" }], "authorizer": { "handler": "allow" }, "mutators": [{ "handler": "noop" }] }, { "id": "rvtechkratosprotected", "upstream": { "url": "http://kratos-svc:4433" }, "match": { "url": "https://synapse-kratos.stg-1.apps.vwg.rivianvw.io/(.*)?$", "methods": ["GET", "POST", "OPTIONS"] }, "authenticators": [{ "handler": "noop" }], "authorizer": { "handler": "allow" }, "mutators": [{ "handler": "noop" }] }, { "id": "rvtechotaprotected", "upstream": { "url": "http://ota.ota-c0.svc.cluster.local", "preserve_host": true }, "match": { "url": "^https?://ota\\.stg-1\\.apps\\.vwg\\.rivianvw\\.io.*", "methods": ["GET", "POST", "OPTIONS"] }, "authenticators": [ { "handler": "cookie_session" } ], "authorizer": { "handler": "allow" }, "mutators": [{ "handler": "id_token" }, { "handler": "header" }], "errors": [{ "handler": "json" }] }, { "id": "rvtechgql routercors-preflight", "upstream": { "url": "http://gql-router-c0-vwg-stg-1-ew1.gql-router-c0.svc.cluster.local:3002", "preserve_host": true }, "match": { "url": "^https?://gql-router\\.stg-1\\.apps\\.vwg\\.rivianvw\\.io.*", "methods": ["OPTIONS"] }, "authenticators": [{ "handler": "anonymous" }], "authorizer": { "handler": "allow" }, "mutators": [{ "handler": "header", "config": { "headers": { "Access-Control-Allow-Origin": "{{ if or (eq .Request.Header.Origin \"https://ota.stg-1.apps.vwg.rivianvw.io\") (eq .Request.Header.Origin \"https://local.rivianvw.io:4299\") }}{{ .Request.Header.Origin }}{{ else }}\"\"{{ end }}", "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Authorization, Content-Type, Accept, Origin, Cookie", "Access-Control-Expose-Headers": "Content-Length", "Access-Control-Allow-Credentials": "true", "Access-Control-Max-Age": "86400" } } }] }, { "id": "rvtechgql routerprotected", "upstream": { "url": "http://gql-router-c0-vwg-stg-1-ew1.gql-router-c0.svc.cluster.local:3002", "preserve_host": true }, "match": { "url": "^https?://gql-router\\.stg-1\\.apps\\.vwg\\.rivianvw\\.io.*", "methods": ["GET", "POST"] }, "authenticators": [{ "handler": "cookie_session" }], "authorizer": { "handler": "allow" }, "mutators": [{ "handler": "id_token" }, { "handler": "header" }] }, { "id": "rvtechuser management middelwareprotected", "upstream": { "url": "http://user-management-middleware.connected-services.svc.cluster.local:80", "preserve_host": true }, "match": { "url": "https://user-management-ui-connected-services.stg-1.apps.vwg.rivianvw.io/(.*)?$", "methods": ["GET", "POST", "OPTIONS", "DELETE", "PATCH", "PUT"] }, "authenticators": [ { "handler": "cookie_session" } ], "authorizer": { "handler": "allow" }, "mutators": [{ "handler": "id_token" }, { "handler": "header" }], "errors": [{ "handler": "json" }] } ] I see a cors error on https://gql-router.stg-1.apps.vwg.rivianvw.io/graphql referrer https://ota.stg-1.apps.vwg.rivianvw.io/
b
A message from kapa.ai
Based on your access rules and the information provided, here’s what can be determined from the knowledge sources: - You have a dedicated CORS preflight rule for the GraphQL router (
rvtech:gql-router:cors-preflight
) that matches
OPTIONS
requests to
<https://gql-router.stg-1.apps.vwg.rivianvw.io>.*
and sets the appropriate CORS headers using the header mutator. - Your protected rule for the GraphQL router (
rvtech:gql-router:protected
) matches
GET
and
POST
requests to the same host and path, and uses the
cookie_session
authenticator. Potential causes for the CORS error: 1. CORS Preflight Rule Matching: The CORS preflight rule only matches
OPTIONS
requests. If the
OPTIONS
request does not match this rule (for example, due to a mismatch in the URL pattern or method), Oathkeeper will not set the required CORS headers, and the browser will block the request. Make sure the
OPTIONS
request to
/graphql
matches the CORS preflight rule's URL pattern exactly. 2. Allowed Origins Logic: In your CORS preflight rule, the
Access-Control-Allow-Origin
header is set only if the
Origin
header matches either
<https://ota.stg-1.apps.vwg.rivianvw.io>
or
<https://local.rivianvw.io:4299>
. If the request comes from a different origin, the header will be set to an empty string, which will cause the browser to block the request. Ensure that the
Origin
header in your request matches one of the allowed origins in your template. 3. Credentials and Headers: Your CORS headers include
Access-Control-Allow-Credentials: true
, which is correct if you are sending cookies. However, the
Access-Control-Allow-Origin
header must not be a wildcard (
*
) when credentials are allowed, and must match the request's
Origin
exactly, which your template attempts to do. 4. Noop Authenticator for Preflight: The CORS preflight rule uses the
anonymous
authenticator, which is correct, as preflight requests do not include cookies. What to check: - Confirm that the
OPTIONS
request to
/graphql
is actually matching the
rvtech:gql-router:cors-preflight
rule. If not, adjust the
match.url
pattern to ensure it matches all relevant paths, including
/graphql
. - Ensure the
Origin
header in your browser request is exactly
<https://ota.stg-1.apps.vwg.rivianvw.io>
(or add any other required origins to your template logic). - If you see a CORS error in the browser, check the network tab to see what CORS headers are being returned (or not returned) in the preflight response. Summary: Your CORS preflight rule is set up in a way that should work if the request matches the rule and the origin is allowed. If you are still seeing CORS errors, it is likely due to the
OPTIONS
request not matching the rule (URL pattern issue) or the
Origin
not being included in your allowed origins logic. Double-check both the rule matching and the allowed origins in your template to resolve the issue. This approach is consistent with how Oathkeeper matches rules and handles CORS, as seen in the error and rule format documentation in the knowledge sources.
b
@chilly-king-10285 How can I split out the email field in this oathkeeper claims mapper into a separate property brand, which only takes the foo@bar.com - the values between @ and . claims: | {{ if .Extra.identity }} { "sub": "{{ .Subject }}", "email": "{{ .Extra.identity.traits.email }}", "name": { "first": "{{ .Extra.identity.traits.name.first }}", "last": "{{ .Extra.identity.traits.name.last }}" } } {{ else }} {} {{ end }}
b
A message from kapa.ai
I'm sorry, but the knowledge sources do not contain information or examples on how to extract a substring (such as the brand from the email address) using templating functions in Ory Oathkeeper's claims mapping. The documentation only describes basic field access and the use of the
print
and
printIndex
functions for safe value extraction in templates, but does not mention support for string manipulation or regular expressions within the claims template [Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token). If you require this functionality, you may need to preprocess the value externally or use a custom mutator, as the built-in templating does not appear to support extracting parts of a string like the domain from an email address.
b
@chilly-king-10285 - 'Failed to revoke session: the request was rejected to protect you from Cross-Site-Request-Forgery',
b
A message from kapa.ai
The error
'Failed to revoke session: the request was rejected to protect you from Cross-Site-Request-Forgery'
indicates that Ory's CSRF protection mechanism was triggered during your attempt to revoke a session. Why this happens: Ory requires a valid CSRF token to be included when making requests that modify state (such as revoking a session). If the CSRF token is missing, invalid, or does not match the anti-CSRF cookie, the request will be rejected to prevent potential CSRF attacks [Defenses against cross-site request forgery](https://www.ory.sh/docs/kratos/concepts/security#defenses-against-cross-site-request-forgery). How to resolve: - For browser-based flows: Ensure that you are sending both the CSRF cookie and the CSRF token (usually as a form field named
csrf_token
) when making the request. The browser should send the cookie automatically, but you must include the CSRF token in the request body or as a header. > "Ory provides CSRF protection for all flows. This means that you must send a CSRF token in the body and CSRF cookie back when submitting a flow." [Bring-your-own-ui: Debug](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#debug) - For AJAX or API calls: If you are making the request programmatically (e.g., from a SPA or server-side code), make sure to forward the
Cookie
header and include the CSRF token in the request body or as the
X-CSRF-Token
header. > "When building Single-Page Apps (SPAs)... you need to configure your AJAX request to include cookies, because AJAX doesn't send cookies by default... you must add the CSRF token manually to the request body." [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf) - If you are using a GET request: Note that CSRF tokens are generally required for state-changing requests (POST, DELETE, etc.), but some endpoints may require them even for GET if they are sensitive. - If you are using a server-side application: Make sure to forward the
Cookie
header from the client and include the CSRF token in the request body when calling the Ory API [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf). Summary: This error is caused by missing or mismatched CSRF tokens. Ensure you are including both the CSRF cookie and the CSRF token in your request when revoking a session. If you are using a custom UI or API client, double-check that you are forwarding all necessary headers and form fields as required by Ory's CSRF protection.
b
@chilly-king-10285 - how do I convert a clientId and secret into a bearer token
b
A message from kapa.ai
To convert a
client_id
and
client_secret
into a bearer token in Ory, you use the OAuth2 Client Credentials Flow. This flow is designed for machine-to-machine authentication and allows you to exchange your client credentials for an access token (bearer token). ### Using the Ory CLI You can perform this flow using the Ory CLI:
Copy code
sh ory perform client-credentials \   --client-id=<your-client-id> \   --client-secret=<your-client-secret> \   --project <your-project-id>
This command will return an access token (bearer token) that you can use to authenticate API requests [Try common OAuth2 Grants](https://www.ory.sh/docs/getting-started/ory-network-oauth2) | [ory perform client-credentials](https://www.ory.sh/docs/cli/ory-perform-client-credentials#ory-perform-client-credentials). ### Using HTTP API You can also perform the client credentials flow directly via HTTP: Basic Authentication:
Copy code
javascript const clientID = "the-client-id" const clientSecret = "the-secret-id" const basicAuth = btoa(encodeURIComponent(clientID) + ":" + encodeURIComponent(clientSecret))  fetch("<https://your-project.projects.oryapis.com/oauth2/token>", {   method: "POST",   headers: {     Authorization: "Basic " + basicAuth,     "Content-Type": "application/x-www-form-urlencoded",   },   body: "grant_type=client_credentials&scope=read", })   .then((response) => response.json())   .then((data) => console.log(data))
This will return a JSON response containing the access token [OAuth2 client credentials flow](https://www.ory.sh/docs/oauth2-oidc/client-credentials). Body Authentication:
Copy code
javascript const qs = new URLSearchParams() qs.set("grant_type", "client_credentials") qs.set("client_id", clientID) qs.set("client_secret", clientSecret) qs.set("scope", "read")  fetch("<https://your-project.projects.oryapis.com/oauth2/token>", {   method: "POST",   headers: { "Content-Type": "application/x-www-form-urlencoded" },   body: qs.toString(), })   .then((response) => response.json())   .then((data) => console.log(data))
This will also return a bearer token if the credentials are valid [OAuth2 client credentials flow](https://www.ory.sh/docs/oauth2-oidc/client-credentials). --- Summary: Use the OAuth2 Client Credentials Flow, either via the Ory CLI or directly via HTTP, to exchange your
client_id
and
client_secret
for a bearer token.
b
@chilly-king-10285 Token fetched using fetch API: { "error": "invalid_scope", "error_description": "The requested scope is invalid, unknown, or malformed. The OAuth 2.0 Client is not allowed to request scope 'read'." }
b
A message from kapa.ai
The error message:
Copy code
{
  "error": "invalid_scope",
  "error_description": "The requested scope is invalid, unknown, or malformed. The OAuth 2.0 Client is not allowed to request scope 'read'."
}
means that the OAuth2 client you are using is not configured to request the
read
scope. In Ory, each OAuth2 client must be explicitly configured with the scopes it is allowed to request. If you try to request a scope that is not allowed for that client, you will receive this error [OAuth2 scopes represent user consent](https://www.ory.sh/docs/oauth2-oidc/overview/oauth2-concepts#oauth2-scopes-represent-user-consent). How to fix: 1. Update the OAuth2 client configuration to include the
read
scope. You can do this when creating or updating the client. For example, using the Ory CLI:
Copy code
sh    ory update oauth2-client --project <project-id> <client-id> \      --scope read
Or when creating a new client:
Copy code
sh    ory create oauth2-client --project <project-id> \      --name "My Client" \      --grant-type client_credentials \      --scope read
See the [Ory OAuth2 client management documentation](https://www.ory.sh/docs/hydra/guides/oauth2-clients) for more details. 2. Verify the allowed scopes for your client. If you are not sure which scopes are allowed, you can retrieve the client configuration and check the
scope
field. If you request a scope that is not configured for the client, Ory will reject the request with the
invalid_scope
error, as you are seeing. If you need to use a different scope, make sure to update both your client configuration and your token request accordingly.
b
@chilly-king-10285 - is this a valid value for the jsonnet mapper? metadata_public: { debug_claims: claims, },
b
A message from kapa.ai
Yes, this is a valid value for the Jsonnet mapper in Ory. The knowledge sources provide an example for debugging social sign-in claims mapping, where the
metadata_public
field is set with a
debug_claims
property containing the full
claims
object:
Copy code
jsonnet local claims = std.extVar('claims'); {   identity: {     traits: {       // Minimal traits mapping - adjust as needed       [if 'email' in claims && claims.email_verified then 'email' else null]: claims.email,       // You might even use traits: {} temporarily if no traits are strictly required     },     metadata_public: {       // Store all received claims       debug_claims: claims     }   }, }
This approach is recommended for debugging purposes to inspect the claims received from the social sign-in provider. Just remember to remove or secure this field in production, as it may expose sensitive information [Social sign-in troubleshooting](https://www.ory.sh/docs/troubleshooting/troubleshooting-social-sign-in#redirect-loops-after-registration).
b
@chilly-king-10285 how can I Analyze the
debug_claims
. This shows what data your Jsonnet has access to via the claims variable. when not using ory network
b
A message from kapa.ai
To analyze the
debug_claims
when not using Ory Network, you should: 1. Add the
debug_claims
field to your Jsonnet mapper
in the
metadata_public
section, as shown in the knowledge sources. This will store the full claims object received from the social sign-in provider in the identity's public metadata. 2. Register or log in with a social provider to trigger the mapping and create or update an identity. 3. Inspect the created identity using your self-hosted Ory Kratos instance. You can do this via the Ory Kratos Admin API (
GET /identities/{id}
) or by querying your database directly, depending on your setup. 4. Look for the
metadata_public.debug_claims
field
in the identity data. This field will contain the claims object that your Jsonnet code had access to, allowing you to see the exact structure and data available for mapping. This process is described in the context of troubleshooting social sign-in flows, where adding
debug_claims
helps you understand what data is available in the
claims
variable for your Jsonnet mapping. The same approach applies whether you are using Ory Network or a self-hosted Ory Kratos instance [Social sign-in troubleshooting](https://www.ory.sh/docs/troubleshooting/troubleshooting-social-sign-in#redirect-loops-after-registration).
b
@chilly-king-10285 - I'm using the following oidc hook for after login.. what is the format of the jsonnet file for the body login: ui_url: https://lxio/login after: # This block is for post-authentication actions after an OIDC login. oidc: hooks: - hook: web_hook config: url: https://user-xxapps.vwg.rivianvw.io/api/identities/hydrate method: POST body: "base64://ZnVuY3Rpb24oY3R4KSB7CiAgICBpZGVudGl0eTogY3R4LmlkZW50aXR5Cn0=" response: parse: true
b
A message from kapa.ai
The format of the Jsonnet file for the
body
in your OIDC after-login webhook should be a Jsonnet function that receives the context object (
ctx
) and returns the desired payload as a JSON object. This is confirmed in the knowledge sources, which show that for Ory Actions and webhooks, the Jsonnet file typically looks like:
Copy code
jsonnet function(ctx) {   identity: ctx.identity }
In your example, the base64-encoded string decodes to:
Copy code
jsonnet function(ctx) {     identity: ctx.identity }
This means the webhook will receive a JSON body like:
Copy code
json {   "identity": { ... } }
You can customize the returned object to include any fields from the
ctx
object, such as
ctx.identity
,
ctx.flow
, or other available context data, as described in the [Ory Actions webhook guide](https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#jsonnet-templating). Summary: The Jsonnet file should export a function that takes
ctx
and returns an object. For example:
Copy code
jsonnet function(ctx) {   identity: ctx.identity }
This matches the format shown in your configuration and in the knowledge sources for webhook body templating.
b
@chilly-king-10285 - what is included in the ctx object
b
A message from kapa.ai
The
ctx
object in Ory webhook Jsonnet templates includes various fields that provide context about the current flow and the identity involved. According to the knowledge sources, for "after" execution paths (such as after login), the following are included in the
ctx
object: - `identity`: The identity object of the user, containing all identity traits and metadata. - `flow`: The flow object, which includes details about the current self-service flow (e.g., login, registration), such as flow ID, type, UI nodes, and more. - `request_headers`: The HTTP request headers. - `request_method`: The HTTP request method (e.g., GET, POST). - `request_url`: The original request URL. - `transient_payload`: Any transient data passed through the flow. Here is an example structure for the
ctx
object in a registration flow:
Copy code
json {   "ctx": {     "flow": { ... },     "identity": { ... },     "request_cookies": { ... },     "request_headers": { ... },     "request_method": "POST",     "request_url": "<https://playground.projects.oryapis.com/self-service/registration?flow=...>",     // ... other fields   } }
You can access these fields in your Jsonnet template, for example:
Copy code
jsonnet function(ctx) {   userId: ctx.identity.id,   email: ctx.identity.traits.email,   flowId: ctx.flow.id,   method: ctx.request_method }
This structure allows you to customize the webhook payload based on the user, the flow, and the request context [Jsonnet templating](https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#jsonnet-templating) | [Define HTTP request](https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#define-http-request).
b
@chilly-king-10285 - I want to call a webhook and return the roles associated with the user on login.. is this possible? When the webhook target returns a 200 OK response code and the response body is a JSON object with the key
identity
in it, the values from that object will be used to change the identity before it is saved to the database. Modifying the identity is only possible during registration and settings flows.
b
A message from kapa.ai
No, it is not possible to modify the identity (such as returning and updating roles) via a webhook during the login flow. According to the knowledge sources, webhooks that can modify the identity by returning a response with the
identity
key are only supported during the registration and settings flows—not during login: > Modifying the identity is only possible during registration and settings flows. [Webhook response handling: Modify identities](https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#modify-identities) During the login flow, you can call a webhook and use its response to interrupt the flow or provide messages, but you cannot update the identity (such as assigning roles) at this stage. If you need to update roles or other identity fields, you must do so during registration, settings, or via the admin API.
b
@chilly-king-10285 - I want to associate roles to a user after login how would I do this with OIDC there is no registration
b
A message from kapa.ai
You cannot update or associate roles to a user (i.e., modify the identity) during the OIDC login flow in Ory. The knowledge sources explicitly state that modifying the identity via a webhook response (by returning an
identity
object) is only possible during registration and settings flows, not during login: > Modifying the identity is only possible during registration and settings flows. [Webhook response handling: Modify identities](https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#modify-identities) If you need to associate roles after login and you are not using registration flows, you must update the user's identity using the admin API or during a settings flow, not as part of the OIDC login process. There is no supported mechanism in Ory to update identity traits (such as roles) as a direct result of a login flow.
b
@chilly-king-10285 - I want to add roles to the token after login how would I do this
b
A message from kapa.ai
To add roles to the token after login in Ory, you should use a webhook (Ory Action) as a post-login hook to fetch or compute the user's roles and include them in the token. While you cannot modify the identity itself during login (modifying the identity is only possible during registration and settings flows), you can use the webhook to add custom claims—such as roles—to the session or token that is issued after login. How to do it: 1. Configure a post-login webhook (Ory Action): In your Ory configuration, set up a webhook under the
after.login.oidc.hooks
section. The webhook can call your service to fetch the user's roles. 2. Return roles in the session or token: The webhook can return a response that includes custom session data (such as roles), which will be included in the session and can be mapped into the token. For example, your webhook could return:
Copy code
json {   "session": {     "id_token": {       "roles": ["admin", "user"]     },     "access_token": {       "roles": ["admin", "user"]     }   } }
This will add the
roles
claim to the ID token and access token issued after login. This approach is described in the Ory documentation for adding custom claims to tokens via webhooks and consent actions [Adding custom claims to tokens](https://www.ory.sh/docs/oauth2-oidc/userinfo-oidc). Important notes: - You cannot update the identity traits (such as roles) during login, but you can add claims to the token/session. - The roles must be fetched or computed by your webhook and returned in the correct format. Summary: Use a post-login webhook to fetch or compute roles and return them as custom claims in the session or token. This allows you to include roles in the tokens issued after login, even though you cannot modify the identity itself during the login flow.
b
@chilly-king-10285 - here is my id_token mapping how do I map the roles coming back from my webhook response
Copy code
id_token:
          enabled: true
          config:
            issuer_url: <https://xxxx.io>
            jwks_url: file:///etc/secrets/mutator.id_token.jwks.json
            ttl: 15m
            claims: |
              {{ if .Extra.identity }}
                {
                  "sub": "{{ .Subject }}",
                  "company_code": "{{ .Extra.identity.traits.company_code }}",
                  "corporation": "{{ .Extra.identity.traits.corporation }}",
                  "cost_center": "{{ .Extra.identity.traits.cost_center }}",
                  "email": "{{ .Extra.identity.traits.email }}",
                  "employee_id": "{{ .Extra.identity.traits.employee_id }}",
                  "first_name": "{{ .Extra.identity.traits.first_name }}",
                  "last_name": "{{ .Extra.identity.traits.last_name }}",
                  "name": "{{ .Extra.identity.traits.name }}",
                  "roles": "{{ .Extra.identity.traits.roles }}",
                  "tenant_id": "{{ .Extra.identity.traits.tenant_id }}"
                }
              {{ else }}
                {} 
              {{ end }}
b
A message from kapa.ai
To map the
roles
field into your
id_token
in Oathkeeper, you should reference the field from the session data available in
.Extra.identity.traits.roles
within your claims template. Your current configuration does this:
Copy code
yaml "roles": "{{ .Extra.identity.traits.roles }}"
However, be aware that this will serialize the roles as a string, even if
roles
is an array in your identity traits. If you want the
roles
claim to be an array in the resulting JWT (which is standard for roles claims), you should avoid quoting the template variable:
Copy code
yaml "roles": {{ .Extra.identity.traits.roles }}
This way, if
roles
is an array in your identity schema, it will be included as a JSON array in the token, not as a string. Example:
Copy code
yaml claims: |   {{ if .Extra.identity }}     {       "sub": "{{ .Subject }}",       "roles": {{ .Extra.identity.traits.roles }}       // ... other claims ...     }   {{ else }}     {}   {{ end }}
Note: The knowledge sources confirm that you can map any field from the identity traits into the token claims using the templating syntax, and that the
id_token
mutator supports this approach [Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token). If
roles
is not present or is
nil
, you may want to add a default value or handle it conditionally to avoid template errors, but the above is the standard way to map an array field from identity traits into the JWT.
A message from kapa.ai
To map the
roles
coming back from your webhook response into the
id_token
in Oathkeeper, you need to ensure that the webhook is returning the roles in a place that Oathkeeper can access in its templating—typically in the session or extra data. In your claims mapping, you are currently referencing
{{ .Extra.identity.traits.roles }}
. If your webhook returns roles as part of the session (for example, in the
id_token
or
access_token
fields of the session object), you should reference them accordingly in your claims template. However, the knowledge sources do not provide explicit documentation on mapping webhook-returned session fields directly into the
id_token
mutator in Oathkeeper. They do confirm that you can map fields from
.Extra
(which contains session data) into the token claims using the templating syntax [Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token). Important: If your webhook returns roles in a different location (e.g., as
session.id_token.roles
), you would need to reference them as
{{ .Extra.id_token.roles }}
in your claims mapping. If roles are an array: To ensure the
roles
claim is a JSON array (not a string), do not wrap the template in quotes:
Copy code
yaml "roles": {{ .Extra.identity.traits.roles }}
If roles are a string or you want to ensure a string: Keep the quotes:
Copy code
yaml "roles": "{{ .Extra.identity.traits.roles }}"
Summary: - Reference the roles in your claims mapping according to where your webhook places them in the session data. - If they are in
.Extra.identity.traits.roles
, use that path. - Remove the quotes if you want the claim to be a JSON array. If your webhook is not placing the roles in
.Extra.identity.traits.roles
, you will need to adjust either your webhook or your claims mapping accordingly. The knowledge sources do not provide more advanced examples or confirm support for more complex mapping scenarios.
b
@chilly-king-10285 -
Copy code
{"audience":"application","error":{"message":"error executing claims template in rule \"rvtech:frame:protected\": template: fce1eec24db43f22059c48d80de3a299:12:23: executing \"fce1eec24db43f22059c48d80de3a299\" at <.Extra.session.id_token.roles>: nil pointer evaluating interface {}.id_token","stack_trace":"\<http://ngithub.com/ory/oathkeeper/pipeline/mutate.(*MutatorIDToken).Mutate|ngithub.com/ory/oathkeeper/pipeline/mutate.(*MutatorIDToken).Mutate>\n\t/project/pipeline/mutate/mutator_id_token.go:152\ngithub.com/ory/oathkeeper/proxy.(*requestHandler).HandleRequest\n\t/project/proxy/request_handler.go:310\ngithub.com/ory/oathkeeper/proxy.(*Proxy).Rewrite\n\t/project/proxy/proxy.go:133\nnet/http/httputil.(*ReverseProxy).ServeHTTP\n\t/usr/local/go/src/net/http/httputil/reverseproxy.go:433\ngithub.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/cmd/server.runProxy.func1.ContextualizedMiddleware.6\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/corsx/middleware.go:28\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/reqlog.(*Middleware).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/reqlog/middleware.go:143\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/metrics.(*Middleware).ServeHTTP\n\t/project/metrics/middleware.go:105\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/metricsx.(*Service).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/metricsx/middleware.go:272\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/urfave/negroni.(*Negroni).ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:96\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.(*middleware).serveHTTP\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go:176\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.NewMiddleware.func1.1\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go:65\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2220\nnet/http.serverHandler.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:3210\nnet/http.(*conn).serve\n\t/usr/local/go/src/net/http/server.go:2092\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1700"},"granted":false,"http_host":"<http://frame.stg-1.apps.vwg.rivianvw.io|frame.stg-1.apps.vwg.rivianvw.io>","http_method":"GET","http_url":"<http://frame.stg-1.apps.vwg.rivianvw.io/>","http_user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36","level":"warning","msg":"The mutation handler encountered an error","mutation_handler":"id_token","reason_id":"mutation_handler_error","rule_id":"rvtech:frame:protected","service_name":"ORY Oathkeeper","service_version":"v0.40.9","subject":"c7255360-9817-44f2-9904-218ebb713e91","time":"2025-08-06T16:38:58.554952391Z"}
{"audience":"application","error":{"message":"error executing claims template in rule \"rvtech:frame:protected\": template: fce1eec24db43f22059c48d80de3a299:12:23: executing \"fce1eec24db43f22059c48d80de3a299\" at <.Extra.session.id_token.roles>: nil pointer evaluating interface {}.id_token","stack_trace":"\<http://ngithub.com/ory/oathkeeper/pipeline/mutate.(*MutatorIDToken).Mutate|ngithub.com/ory/oathkeeper/pipeline/mutate.(*MutatorIDToken).Mutate>\n\t/project/pipeline/mutate/mutator_id_token.go:152\ngithub.com/ory/oathkeeper/proxy.(*requestHandler).HandleRequest\n\t/project/proxy/request_handler.go:310\ngithub.com/ory/oathkeeper/proxy.(*Proxy).Rewrite\n\t/project/proxy/proxy.go:133\nnet/http/httputil.(*ReverseProxy).ServeHTTP\n\t/usr/local/go/src/net/http/httputil/reverseproxy.go:433\ngithub.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/cmd/server.runProxy.func1.ContextualizedMiddleware.6\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/corsx/middleware.go:28\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/reqlog.(*Middleware).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/reqlog/middleware.go:143\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/metrics.(*Middleware).ServeHTTP\n\t/project/metrics/middleware.go:105\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/metricsx.(*Service).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/metricsx/middleware.go:272\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/urfave/negroni.(*Negroni).ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:96\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.(*middleware).serveHTTP\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go:176\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.NewMiddleware.func1.1\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go:65\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2220\nnet/http.serverHandler.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:3210\nnet/http.(*conn).serve\n\t/usr/local/go/src/net/http/server.go:2092\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1700"},"granted":false,"http_host":"<http://frame.stg-1.apps.vwg.rivianvw.io|frame.stg-1.apps.vwg.rivianvw.io>","http_method":"GET","http_url":"<http://frame.stg-1.apps.vwg.rivianvw.io/>","http_user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36","level":"warning","msg":"Access request denied","service_name":"ORY Oathkeeper","service_version":"v0.40.9","time":"2025-08-06T16:38:58.555253937Z"}
{"audience":"application","error":{"debug":"","message":"An internal server error occurred, please contact the system administrator","reason":"","stack_trace":"\<http://ngithub.com/ory/oathkeeper/pipeline/mutate.(*MutatorIDToken).Mutate|ngithub.com/ory/oathkeeper/pipeline/mutate.(*MutatorIDToken).Mutate>\n\t/project/pipeline/mutate/mutator_id_token.go:152\ngithub.com/ory/oathkeeper/proxy.(*requestHandler).HandleRequest\n\t/project/proxy/request_handler.go:310\ngithub.com/ory/oathkeeper/proxy.(*Proxy).Rewrite\n\t/project/proxy/proxy.go:133\nnet/http/httputil.(*ReverseProxy).ServeHTTP\n\t/usr/local/go/src/net/http/httputil/reverseproxy.go:433\ngithub.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/cmd/server.runProxy.func1.ContextualizedMiddleware.6\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/corsx/middleware.go:28\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/reqlog.(*Middleware).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/reqlog/middleware.go:143\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/oathkeeper/metrics.(*Middleware).ServeHTTP\n\t/project/metrics/middleware.go:105\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/ory/x/metricsx.(*Service).ServeHTTP\n\t/go/pkg/mod/github.com/ory/x@v0.0.677/metricsx/middleware.go:272\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38\ngithub.com/urfave/negroni.(*Negroni).ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:96\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.(*middleware).serveHTTP\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go:176\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.NewMiddleware.func1.1\n\t/go/pkg/mod/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@v0.57.0/handler.go:65\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2220\nnet/http.serverHandler.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:3210\nnet/http.(*conn).serve\n\t/usr/local/go/src/net/http/server.go:2092\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1700","status":"Internal Server Error","status_code":500},"http_request":{"headers":{"accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","accept-encoding":"gzip, deflate, br, zstd","accept-language":"en-US,en;q=0.9","cache-control":"no-cache","cookie":["__reveal_ut=6014eb61-1bfb-4449-4123-4f2d5665e511; ory_kratos_session=MTc1NDQ1ODM5MXw5QWhoak10VnVxUS02eEswRXRUTVpOWG9fZmhGQ2N5TUxmb2szSVFiYkNXMVdPVVFERWFJUXR6OFJfOGoxWW9mRnFTSV9XWVVIWE05NTV6b3Z3SmhlbC1FSTE4YVdGTlFUSUllT0RDQ2R6VWcya0c1UDNDc0VTRmt2MWFQX2E0Y1BlbGZIRmxZVFdtR1dHdlFfN1ZZN3Nid0xvb2JnUkxsaDVFUEM0MUF6RkJ0WFdjaldTeW83ZWlvUlZYV3d5eFpnM2laeERkU1p2VmY5SDNhU1lWbFE2SEw0V2xHcnZqVEl5a3Y5NkJyZ2hKZDM0ZzZickpWM2RwcmZVa2FrT29ZaTlMQnhBb2tGdGtRSU04Wm9TTEJ8zgQVBDYNdEMguiPLwdgklS3vVzJwOSV-7G_tab6Ufoo=; __reveal_ut=65a6b2aa-1dcd-4272-5ec1-fb5bdcb5a3f3"],"pragma":"no-cache","referer":"<https://login.stg-1.apps.vwg.rivianvw.io/>","sec-ch-ua":"\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"macOS\"","sec-fetch-dest":"document","sec-fetch-mode":"navigate","sec-fetch-site":"same-site","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36","x-forwarded-port":"80","x-forwarded-scheme":"http","x-real-ip":"10.223.103.86","x-request-id":"a7ba32649471c84adcecb08809f4da81","x-scheme":"http"},"host":"<http://frame.stg-1.apps.vwg.rivianvw.io|frame.stg-1.apps.vwg.rivianvw.io>","method":"GET","path":"/","query":null,"remote":"100.64.88.246:57926","scheme":"http"},"http_response":{"status_code":500},"level":"error","msg":"An error occurred while handling a request","service_name":"ORY Oathkeeper","service_version":"v0.40.9","time":"2025-08-06T16:38:58.555560093Z"}
b
A message from kapa.ai
Your error:
Copy code
error executing claims template in rule "rvtech:frame:protected": template: ... at <.Extra.session.id_token.roles>: nil pointer evaluating interface {}.id_token
means that your claims template is trying to access
.Extra.session.id_token.roles
, but for this request,
.Extra.session.id_token
is
nil
(does not exist). This results in a nil pointer error and causes Oathkeeper to return a 500 error and deny the request. What this means: - The field
.Extra.session.id_token.roles
is not always present in the session data for every request. - When it is missing, trying to access
.roles
on a
nil
value causes the template to fail. How to fix: You need to make your claims template resilient to missing data. The knowledge sources confirm that you must avoid referencing fields that may not exist for all request types, especially for unauthenticated or anonymous requests, or when your webhook does not return the expected structure. The Oathkeeper documentation recommends using conditional logic to check for the existence of fields before accessing them in your template ([Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token)). Example fix:
Copy code
yaml claims: |   {{ if .Extra.session }}     {{ if .Extra.session.id_token }}       {         "roles": {{ .Extra.session.id_token.roles }}         // ... other claims ...       }     {{ else }}       {         "roles": []         // ... other claims ...       }     {{ end }}   {{ else }}     {       "roles": []       // ... other claims ...     }   {{ end }}
This ensures that if
.Extra.session.id_token
is missing, the
roles
claim will default to an empty array, preventing the nil pointer error. Summary: Your error is caused by referencing a field that may not exist in all cases. Use conditional checks in your claims template to ensure the field exists before accessing it, as recommended in the Oathkeeper documentation. This will prevent nil pointer errors and allow your rule to work correctly.
b
@chilly-king-10285 - is there a way to log all of the .Extra properties
b
A message from kapa.ai
The knowledge sources do not explicitly mention a built-in way in Ory Oathkeeper to log all of the
.Extra
properties directly through configuration or templating. The documentation describes how
.Extra
contains arbitrary session data and how you can reference its fields in mutators and templates, but it does not provide a method for dumping or logging the entire
.Extra
object for debugging or inspection purposes [Session variables](https://www.ory.sh/docs/oathkeeper/pipeline#session). If you need to inspect the contents of
.Extra
, you would typically do so by referencing specific fields in your mutator templates or by adding them to a custom header or claim for debugging. However, there is no documented feature for logging or outputting all
.Extra
properties automatically.
b
@chilly-king-10285 - is this the correct syntax for a webhook response
Copy code
selfservice:
  # ...
  flows:
    # ...
    login:
      after:
        oidc:
          hooks:
          - config:
              body: <base64://ZnVuY3Rpb24oY3R4KSB7CiAgICBpZDogY3R4LmlkZW50aXR5LmlkLAogICAgc2N>oZW1hX2lkOiBjdHguaWRlbnRpdHkuc2NoZW1hX2lkLAogICAxxxCAgIH0KfQ==
              method: POST
              response:
                parse: true
              url: <https://xxx/api/identities/hydrate>
            hook: web_hook
          - # New `session` hook to use data from the webhook
            hook: session
            config:
              payload:
                id_token:
                  # Use data from the webhook response. `id_token` is from the response, not kratos context
                  roles: "{{ .webhook_response.body.session.id_token.roles }}"
                access_token:
                  # Use data from the webhook response. `access_token` is from the response, not kratos context
                  roles: "{{ .webhook_response.body.session.access_token.roles }}"
      ui_url: <https://login.stg-1.apps.vwg.rivianvw.io/login>
b
A message from kapa.ai
Based on the knowledge sources, your webhook response syntax is mostly correct for configuring an Ory Action (webhook) after OIDC login, but there are a few important clarifications and limitations: 1. Webhook Response Usage: The knowledge sources confirm that you can use a webhook (with
parse: true
) to call an external service after login, and then use the webhook's response to populate session or token claims via the
session
hook. The
session
hook can reference data from the webhook response using the
webhook_response.body
path ([Customize login and registration behavior](https://www.ory.sh/docs/identities/sign-in/actions#customize-login-and-registration-behavior)). 2. Referencing Webhook Response: The syntax you use for referencing roles from the webhook response in the
session
hook is correct:
Copy code
yaml    roles: "{{ .webhook_response.body.session.id_token.roles }}"
This matches the documented approach for using data returned by the webhook in subsequent hooks. 3. Order of Hooks: The knowledge sources highlight that the order of hooks matters. The
web_hook
should come before the
session
hook, so the session hook can use the data returned by the webhook. Your configuration follows this order, which is correct ([Github Issue: after registration hook is not fired](https://github.com/ory/network/issues/361)). 4. Base64-Encoded Jsonnet: The
body
field for the webhook is expected to be a base64-encoded Jsonnet function, which your configuration provides. This is the correct format ([Jsonnet templating](https://www.ory.sh/docs/guides/integrate-with-ory-cloud-through-webhooks#jsonnet-templating)). 5. Limitation: The knowledge sources do not provide a full example of the exact structure of the
webhook_response.body
object, but they do confirm that this is the intended way to pass data from a webhook to the session/token claims. Summary: Your configuration is correct according to the knowledge sources: - The webhook is called after OIDC login. - The webhook response is parsed and can be referenced in the following
session
hook. - The
roles
claim is mapped from the webhook response into the session's
id_token
and
access_token
. If you encounter issues, ensure your webhook returns the expected structure in its response body, as the session hook will look for
body.session.id_token.roles
and
body.session.access_token.roles
in the webhook response.
b
@chilly-king-10285 - I am not seeing the roles being added to the session token.. here is my config
Copy code
login:
            ui_url: <https://login.stg-1.apps.vwg.rivianvw.io/login>
            after:
              oidc:
                hooks:
                  - hook: web_hook
                    config:
                      url: <https://user-management-middleware-production-tooling.stg-1.apps.vwg.rivianvw.io/api/identities/hydrate>
                      method: POST
                      body: "<base64://ZnVuY3Rpb24oY3R4KSB7CiAgICBpZDogY3R4LmlkZW50aXR5LmlkLAogICAgc2N>oZW1hX2lkOiBjdHguaWRlbnRpdHkuc2NoZW1hX2lkLAogICAgdHJhaXRzOiB7CiAgICAgIGVtYWlsOiBjdHguaWRlbnRpdHkudHJhaXRzLmVtYWlsLAogICAgICBlbXBsb3llZV9pZDogY3R4LmlkZW50aXR5LnRyYWl0cy5lbXBsb3llZV9pZCwKICAgICAgY29tcGFueV9jb2RlOiBjdHguaWRlbnRpdHkudHJhaXRzLmNvbXBhbnlfY29kZSwKICAgICAgY29ycG9yYXRpb246IGN0eC5pZGVudGl0eS50cmFpdHMuY29ycG9yYXRpb24sCiAgICAgIHRlbmFudF9pZDogY3R4LmlkZW50aXR5LnRyYWl0cy50ZW5hbnRfaWQKICAgIH0KfQ=="
                      response:
                        parse: true
                  - hook: session
                    config:
                      payload:
                        id_token:
                          roles: "{{ .webhook_response.body.session.id_token.roles }}"
                        access_token:
                          roles: "{{ .webhook_response.body.session.access_token.roles }}"
the object sent to the webhok
Copy code
{
  id: 'c725asdfgagda-218ebb713e91',
  schema_id: 'default',
  traits: {
    company_code: '0000',
    corporation: 'foo',
    email: 'swestergaard@foo.tech',
    employee_id: '164f3xxxxd-a86a-a765bc9exx2c',
    tenant_id: '9be208xxe-bdx54668e3'
  }
}
my webhook response is
Copy code
{
  "session": {
    "id_token": {
      "roles": "ota-admin"
    },
    "access_token": {
      "roles": [
        "admin",
        "user"
      ]
    }
  }
}
my ory cookisession with values obfuscated, roles is an empty string
Copy code
{
  "identity": {
    "id": "obfuscated-id-a",
    "schema_id": "default",
    "schema_url": "obfuscated-url",
    "state": "active",
    "state_changed_at": "obfuscated-date-time",
    "traits": {
      "email": "obfuscated-user@obfuscated.tech",
      "roles": "",
      "fullname": "Obfuscated User",
      "last_name": "User",
      "tenant_id": "obfuscated-tenant-id",
      "first_name": "Obfuscated",
      "corporation": "Obfuscated Corp",
      "cost_center": "XXXX",
      "employee_id": "obfuscated-employee-id",
      "company_code": "XXXX",
      "debug_claims": {
        "iss": "obfuscated-issuer-url",
        "sub": "obfuscated-subject-id",
        "name": "Obfuscated User",
        "email": "obfuscated-user@obfuscated.tech",
        "raw_claims": {
          "rh": "obfuscated-raw-claim-rh",
          "aud": "obfuscated-audience-id",
          "exp": 0,
          "iat": 0,
          "iss": "obfuscated-raw-claim-issuer-url",
          "nbf": 0,
          "oid": "obfuscated-oid",
          "sid": "obfuscated-session-id",
          "sub": "obfuscated-raw-claim-subject-id",
          "tid": "obfuscated-raw-claim-tenant-id",
          "uti": "obfuscated-unique-token-identifier",
          "ver": "X.X",
          "name": "Obfuscated User",
          "email": "obfuscated-user@obfuscated.tech",
          "preferred_username": "obfuscated-user@obfuscated.tech"
        },
        "preferred_username": "obfuscated-user@obfuscated.tech"
      }
    }
  }
}
b
A message from kapa.ai
Based on your configuration and the knowledge sources, your setup for calling a webhook after OIDC login and attempting to add roles to the session token is correct in principle. You are: - Calling a webhook with a parsed response. - Using the
session
hook to map roles from the webhook response into the
id_token
and
access_token
claims. However, you are observing that the
roles
field in the session remains an empty string, even though your webhook response contains the expected roles. Key points from the knowledge sources: - The session hook can use data from the webhook response via
.webhook_response.body
if the webhook is configured with
response.parse: true
and is ordered before the session hook. Your configuration matches this requirement ([Customize login and registration behavior](https://www.ory.sh/docs/identities/sign-in/actions#customize-login-and-registration-behavior), [Github Issue: after registration hook is not fired](https://github.com/ory/network/issues/361)). - The session hook only affects the session and token claims, not the identity traits. The
roles
field in your identity traits will not be updated by the session hook, only the session payload and resulting tokens will have the roles if mapped correctly. Possible reasons for not seeing roles in the session token: 1. Where are you checking for roles? The session hook does not update the identity traits (e.g.,
traits.roles
). It only affects the session and the claims in the tokens. If you are inspecting the identity object (as in your cookie session dump), you will not see the roles there. You need to inspect the actual ID token or access token issued after login to see if the roles are present. 2. Webhook response structure: Your webhook response is:
Copy code
json    {      "session": {        "id_token": { "roles": "ota-admin" },        "access_token": { "roles": ["admin", "user"] }      }    }
This matches the expected structure for the session hook to map roles. 3. Token inspection: If you are not seeing the roles in the actual ID token or access token, ensure you are decoding the correct token and that the mapping in the session hook matches the structure of your webhook response. Summary: - The session hook does not update identity traits; it only affects the session and token claims. - To see the roles, inspect the ID token or access token, not the identity traits in the session. - Your configuration is correct for mapping roles from the webhook response into the token claims, as supported by the knowledge sources. If you are still not seeing the roles in the tokens, double-check the actual tokens issued after login and ensure you are decoding them correctly. If the issue persists, the knowledge sources do not provide further troubleshooting steps for this specific scenario.
b
@chilly-king-10285 - i do not see the id_token / access_token here in my headers.. here is the code
Copy code
uthenticators:
        cookie_session:
          enabled: true
          config:
            check_session_url: <http://some-whoami-url>
            preserve_path: true
            preserve_query: true
            subject_from: "identity.id"
            extra_from: "@this"
            only:
              - ory_kratos_session

        anonymous:
          enabled: true
          config:
            subject: guest
        noop:
          enabled: true

      authorizers:
        allow:
          enabled: true
        deny:
          enabled: true
        keto_engine_acp_ory:
          enabled: false
          config:
            base_url: <https://keto-instance.com>
            required_action: ""
            required_resource: ""

      mutators:
        noop:
          enabled: true
        id_token:
          enabled: true
          config:
            issuer_url: <https://some-issuer-url>
            jwks_url: file:///etc/secrets/mutator.id_token.jwks.json
            ttl: 15m
            claims: |
              {{ if .Extra.identity }}
                {
                  "sub": "{{ .Subject }}",
                  "company_code": "{{ .Extra.identity.traits.company_code }}",
                  "corporation": "{{ .Extra.identity.traits.corporation }}",
                  "cost_center": "{{ .Extra.identity.traits.cost_center }}",
                  "email": "{{ .Extra.identity.traits.email }}",
                  "employee_id": "{{ .Extra.identity.traits.employee_id }}",
                  "first_name": "{{ .Extra.identity.traits.first_name }}",
                  "last_name": "{{ .Extra.identity.traits.last_name }}",
                  "name": "{{ .Extra.identity.traits.name }}",
                  {{ if .Extra.session }}
                    {{ if .Extra.session.id_token }}
                      "roles": "roles.session_id,
                    {{ else }}
                      "roles": "roles.empty"
                    {{ end }}
                  {{ end }}
                  "tenant_id": "{{ .Extra.identity.traits.tenant_id }}"
                }
              {{ else }}
                {} 
              {{ end }}

        header:
          enabled: true
          config:
            headers:
              X-User-Subject: "{{ if .Extra.identity }}{{ .Subject }}{{ else }}anonymous{{ end }}"

      log:
        level: trace
        format: json
        leak_sensitive_values: true
        redaction_text: ""
identity-schema
Copy code
identitySchemas:
      "identity.default.schema.json": |
        {
           "$id": "<https://schemas.ory.sh/presets/kratos/identity.email.schema.json>",
           "$schema": "<http://json-schema.org/draft-07/schema#>",
           "title": "Person",
           "type": "object",
           "properties": {
             "traits": {
               "type": "object",
               "properties": {
                 "email": {
                   "type": "string",
                   "format": "email",
                   "title": "E-Mail",
                   "<http://ory.sh/kratos|ory.sh/kratos>": {
                     "credentials": {
                       "password": {
                         "identifier": true
                       },
                       "webauthn": {
                         "identifier": true
                       },
                       "passkey": {
                         "display_name": true
                       }
                     },
                     "recovery": {
                       "via": "email"
                     },
                     "verification": {
                       "via": "email"
                     }
                   }
                 },
                 "company_code": {
                   "type": "string",
                   "title": "Company Code"
                 },
                 "corporation": {
                   "type": "string",
                   "title": "Corporation"
                 },
                 "cost_center": {
                   "type": "string",
                   "title": "Cost Center"
                 },
                 "employee_id": {
                   "type": "string",
                   "title": "Employee ID"
                 },
                 "first_name": {
                   "title": "First Name",
                   "type": "string"
                 },
                 "last_name": {
                   "title": "Last Name",
                   "type": "string"
                 },
                 "roles": {
                   "type": "string",
                   "title": "Roles"
                 },
                 "tenant_id": {
                   "type": "string",
                   "title": "Tenant ID"
                 }
               },
               "required": [
                 "email"
               ],
               "additionalProperties": true
             }
           }
         }
oathkeeper entry
Copy code
{
          "id": "xxx:frame:protected",
          "upstream": {
            "url": "<http://xxx-svc>"
          },
          "match": {
            "url": "<http|https>://xxx.io/<(.*)?$>",
            "methods": ["GET", "POST", "PUT", "DELETE", "PATCH"]
          },
          "authenticators": [{ "handler": "cookie_session" }],
          "authorizer": {
            "handler": "allow"
          },
          "mutators": [{ "handler": "id_token" }, { "handler": "header" }]
        },
b
A message from kapa.ai
Based on your configuration and the knowledge sources, the Oathkeeper
id_token
mutator is responsible for generating a JWT and, by default, setting it as the
Authorization: Bearer <jwt>
header for the proxied request. You do not need to manually set this header in the
header
mutator; the
id_token
mutator does this automatically if it is enabled and configured correctly [Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token). Why you might not see the `id_token`/`access_token` in your headers: 1. The
id_token
mutator only sets the
Authorization
header for the proxied request to the upstream service.
You will not see this header in the response from Oathkeeper to the client/browser, but it will be present in the request Oathkeeper sends to your upstream service. 2. If you are inspecting the response headers in your browser or client, you will not see the
Authorization: Bearer <jwt>
header.
This is expected behavior, as Oathkeeper injects the header when forwarding the request to the upstream, not in the response to the client. 3. If you want to debug or verify the header, you need to check the request received by your upstream service (e.g., by logging incoming headers on your backend). 4. Your identity schema defines
roles
as a string, not an array.
If you want to include multiple roles, you should update your schema to use an array type, and ensure your claims mapping does not wrap the roles in quotes if you want a JSON array in the token. Summary: - The
id_token
mutator sets the
Authorization: Bearer <jwt>
header for upstream requests, not for responses to the client. - You will not see this header in the browser or client response; you must check your upstream service to verify it. - Your configuration is correct for Oathkeeper's documented behavior [Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token). If you need to expose the token to the client, you would need a different approach, as Oathkeeper is designed to inject the token for upstream service authentication, not for client-side use.
b
@chilly-king-10285 - this is the proxied service backend headers, no bearer token is appearing.
Copy code
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [IdentitiesController] Entire Payload: {"id":"[Obfuscated User ID]","schema_id":"default","traits":{"company_code":"XXXX","corporation":"[Obfuscated Corporation]","email":"[Obfuscated Email]","employee_id":"[Obfuscated Employee ID]","tenant_id":"[Obfuscated Tenant ID]"}}
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [IdentitiesController] User [Obfuscated Email] (ID: [Obfuscated User ID]) has roles: [Obfuscated Role]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [IdentitiesController] Returning session data for user [Obfuscated Email] (ID: [Obfuscated User ID]): {"session":{"id_token":{"roles":"[Obfuscated Role]"},"access_token":{"roles":["[Obfuscated Role 1]","[Obfuscated Role 2]"]}}}
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] Received request for /sessions/whoami
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] Incoming Headers:
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] host: [Obfuscated Host]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] x-request-id: [Obfuscated Request ID]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] x-real-ip: [Obfuscated IP]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] x-forwarded-for: [Obfuscated IP]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] x-forwarded-host: [Obfuscated Host]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] x-forwarded-port: XX
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] x-forwarded-proto: [Obfuscated Protocol]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] x-forwarded-scheme: [Obfuscated Scheme]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] x-scheme: [Obfuscated Scheme]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] sec-ch-ua-platform: "[Obfuscated Platform]"
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] user-agent: [Obfuscated User Agent]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] sec-ch-ua: [Obfuscated UA 1], "[Obfuscated UA 2]", "[Obfuscated UA 3]"
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] sec-ch-ua-mobile: ?X
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] accept: /
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] origin: [Obfuscated Origin URL]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] sec-fetch-site: [Obfuscated Fetch Site]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] sec-fetch-mode: [Obfuscated Fetch Mode]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] sec-fetch-dest: [Obfuscated Fetch Destination]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] referer: [Obfuscated Referer URL]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] accept-encoding: [Obfuscated Encoding]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] accept-language: [Obfuscated Language]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] cookie: [Obfuscated Cookie 1]; [Obfuscated Cookie 2]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] if-none-match: [Obfuscated ETag]
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] Incoming Cookies (from @Cookies decorator):
[Nest] 62 - [Obfuscated Date], [Obfuscated Time] LOG [SessionsController] Object(2) {
ory_kratos_session: '[Obfuscated Session Cookie]',
__reveal_ut: '[Obfuscated UT Cookie]'
b
A message from kapa.ai
Based on your backend logs and the knowledge sources, the proxied service is not receiving a Bearer token in the headers from Oathkeeper, even though you have the
id_token
mutator enabled and configured. According to the Oathkeeper documentation, the
id_token
mutator is supposed to generate a JWT and set it as the
Authorization: Bearer <jwt>
header for the proxied request to the upstream service ([Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token)). If you are not seeing this header in your backend, here are the most likely reasons based on the knowledge sources: 1. The route is not matching the rule with the
id_token
mutator.
If the request does not match the Oathkeeper access rule that includes the
id_token
mutator, the header will not be set. 2. The mutator is not being executed due to an error. If there is an error in the mutator configuration or in the claims template, the mutator may fail silently or log an error, and the header will not be set. Check your Oathkeeper logs for any errors related to the
id_token
mutator. 3. The upstream service is not receiving the header due to an intermediary (proxy, ingress, etc.) stripping or not forwarding the
Authorization
header.
The knowledge sources do not provide explicit troubleshooting steps for this, but it is a common issue in reverse proxy setups. 4. The request being inspected is not one that should have the Bearer token. For example, if you are inspecting a request to
/sessions/whoami
, which is typically used for session validation, Oathkeeper may not be configured to add the
Authorization
header for that endpoint, depending on your rules. What you should do: - Check that the request matches the Oathkeeper rule with the
id_token
mutator.
- Review Oathkeeper logs for any errors related to the mutator. - Ensure there is no intermediary stripping the
Authorization
header.
- Verify that the claims template in your
id_token
mutator is not causing errors.
If all of the above are correct and you are still not seeing the header, the knowledge sources do not provide further troubleshooting steps for this specific scenario. The expected behavior, as documented, is that the
id_token
mutator will set the
Authorization: Bearer <jwt>
header for proxied requests when configured and executed correctly.
b
@chilly-king-10285 - my trait mapping
Copy code
claims: |
              {{ if .Extra.identity }}
                {
                  "sub": "{{ .Subject }}",
                  "company_code": "{{ .Extra.identity.traits.company_code }}",
                  "corporation": "{{ .Extra.identity.traits.corporation }}",
                  "cost_center": "{{ .Extra.identity.traits.cost_center }}",
                  "email": "{{ .Extra.identity.traits.email }}",
                  "employee_id": "{{ .Extra.identity.traits.employee_id }}",
                  "first_name": "{{ .Extra.identity.traits.first_name }}",
                  "last_name": "{{ .Extra.identity.traits.last_name }}",
                  "fullname": "{{ .Extra.identity.traits.fullname }}",
                  {{ if .Extra.session }}
                    {{ if .Extra.session.id_token }}
                      "roles": "roles.session_id",
                    {{ else }}
                      "roles": "roles.empty",
                    {{ end }}
                  {{ else }}
                    "roles": "roles.anonymous",
                  {{ end }}
                  "tenant_id": "{{ .Extra.identity.traits.tenant_id }}"
                }
              {{ else }}
                {}
              {{ end }}
Does this take into account the response from the webhook? when the webhook response in this format..
Copy code
{
  "session": {
    "id_token": {
      "roles": "admin"
    },
    "access_token": {
      "roles": [
        "admin",
        "user"
      ]
    }
  }
}
b
A message from kapa.ai
Based on the knowledge sources, your current trait mapping in the
claims
template:
Copy code
jinja {{ if .Extra.identity }}   {     ...     {{ if .Extra.session }}       {{ if .Extra.session.id_token }}         "roles": "roles.session_id",       {{ else }}         "roles": "roles.empty",       {{ end }}     {{ else }}       "roles": "roles.anonymous",     {{ end }}     ...   } {{ else }}   {} {{ end }}
does not actually map the
roles
value from your webhook response. Instead, it sets the
roles
claim to the literal string
"roles.session_id"
,
"roles.empty"
, or
"roles.anonymous"
depending on the presence of
.Extra.session.id_token
. To map the actual roles value returned by your webhook (e.g.,
{"session":{"id_token":{"roles":"admin"}}}
), you need to reference the value directly in your template, like this:
Copy code
jinja "roles": {{ .Extra.session.id_token.roles }}
or, if you want it as a string:
Copy code
jinja "roles": "{{ .Extra.session.id_token.roles }}"
Important: - The knowledge sources confirm that you can use fields from
.Extra
(which contains session data, including webhook responses) in your claims mapping, but you must reference the actual field, not a static string [Oathkeeper Mutators: id_token](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token). - If
.Extra.session.id_token.roles
may not always be present, you should add conditional logic to avoid nil pointer errors, as described in the documentation. Summary: Your current mapping does not use the roles value from the webhook response. To include the roles from the webhook, reference
.Extra.session.id_token.roles
directly in your claims template.
A message from kapa.ai
Yes, your syntax for the webhook response configuration is correct according to the knowledge sources. Here’s why: - The
web_hook
is configured with a
body
(base64-encoded Jsonnet),
method
,
url
, and
response.parse: true
, which is the expected format for an Ory Action webhook after OIDC login. - The
session
hook is placed after the
web_hook
and uses the
payload
field to map values from the webhook response into the
id_token
and
access_token
claims. The syntax
{{ .webhook_response.body.session.id_token.roles }}
and
{{ .webhook_response.body.session.access_token.roles }}
is correct for referencing data returned by the webhook, as described in the documentation and examples for customizing login and registration behavior with Ory Actions and webhooks. The order of hooks is also important and your configuration follows this requirement, ensuring the session hook can access the webhook response [Customize login and registration behavior](https://www.ory.sh/docs/identities/sign-in/actions#customize-login-and-registration-behavior) [Github Issue: after registration hook is not fired](https://github.com/ory/network/issues/361). Summary: Your configuration is correct for calling a webhook after OIDC login and mapping data from the webhook response into the session token claims using the session hook.
b
@chilly-king-10285 - provide an example of the response of a webhook after login
b
A message from kapa.ai
Here is an example of a webhook response after login, as supported by Ory’s webhook integration. When you want to add custom claims (such as roles) to the session or tokens, your webhook should return a JSON object structured like this:
Copy code
json {   "session": {     "id_token": {       "roles": ["admin", "user"]     },     "access_token": {       "roles": ["admin", "user"]     }   } }
This response allows the subsequent
session
hook in your Ory configuration to map the
roles
claim from the webhook response into the
id_token
and
access_token
claims, as described in the documentation for customizing login and registration behavior with Ory Actions and webhooks. The keys and structure must match what your session hook expects to reference (e.g.,
{{ .webhook_response.body.session.id_token.roles }}
) [Customize login and registration behavior](https://www.ory.sh/docs/identities/sign-in/actions#customize-login-and-registration-behavior).
b
@chilly-king-10285 - can I update my token after login from a web hook response
b
A message from kapa.ai
Yes, you can update your token after login using data from a webhook response in Ory. This is achieved by configuring a post-login webhook (Ory Action) that returns custom claims (such as roles) in its response. You then use a
session
hook to map those claims into the
id_token
and
access_token
that are issued after login. The webhook should return a response like:
Copy code
json {   "session": {     "id_token": {       "roles": ["admin", "user"]     },     "access_token": {       "roles": ["admin", "user"]     }   } }
In your Ory configuration, you reference these values in the
session
hook using the syntax:
Copy code
yaml id_token:   roles: "{{ .webhook_response.body.session.id_token.roles }}" access_token:   roles: "{{ .webhook_response.body.session.access_token.roles }}"
This approach is documented in the Ory knowledge sources and is the recommended way to enrich tokens with additional claims based on webhook responses after login [Customize login and registration behavior](https://www.ory.sh/docs/identities/sign-in/actions#customize-login-and-registration-behavior).
b
@chilly-king-10285 - where is this documented
Copy code
id_token:
  roles: "{{ .webhook_response.body.session.id_token.roles }}"
access_token:
  roles: "{{ .webhook_response.body.session.access_token.roles }}"
b
A message from kapa.ai
The specific syntax:
Copy code
yaml id_token:   roles: "{{ .webhook_response.body.session.id_token.roles }}" access_token:   roles: "{{ .webhook_response.body.session.access_token.roles }}"
is not explicitly documented in the knowledge sources provided. However, the general approach—using a webhook (with
response.parse: true
) to return a JSON object, and then referencing fields from the webhook response in a subsequent
session
hook using the
.webhook_response.body
path—is described in the Ory documentation for customizing login and registration behavior with Ory Actions and webhooks. The documentation confirms that you can use data from the webhook response in the session hook, but it does not provide this exact YAML example or confirm the precise syntax for nested fields like
session.id_token.roles
 [Customize login and registration behavior](https://www.ory.sh/docs/identities/sign-in/actions#customize-login-and-registration-behavior). If you need confirmation of this exact syntax, it is recommended to consult the official Ory documentation or test the configuration in your environment.
b
@chilly-king-10285 - how do i handle the webhook response from an after login web_hook. provide an example
b
A message from kapa.ai
To handle the webhook response from an
after login
web_hook
in Ory, you configure your login flow to include a
web_hook
(with
response.parse: true
) followed by a
session
hook. The webhook should return a JSON response, and you can reference fields from this response in the
session
hook using the
.webhook_response.body
path. Example configuration:
Copy code
yaml login:   after:     oidc:       hooks:         - hook: web_hook           config:             url: <https://your-webhook-endpoint>             method: POST             body: "<base64-encoded-jsonnet>"             response:               parse: true         - hook: session           config:             payload:               id_token:                 roles: "{{ .webhook_response.body.session.id_token.roles }}"               access_token:                 roles: "{{ .webhook_response.body.session.access_token.roles }}"
Example webhook response:
Copy code
json {   "session": {     "id_token": {       "roles": ["admin", "user"]     },     "access_token": {       "roles": ["admin", "user"]     }   } }
In this setup, the
session
hook can access the webhook response and map the
roles
claim into the tokens. The documentation confirms that you can use data from the webhook response in the session hook, but does not provide a full YAML example or confirm the precise syntax for nested fields like `session.id_token.roles`—so you may need to test this in your environment to confirm the exact structure [Customize login and registration behavior](https://www.ory.sh/docs/identities/sign-in/actions#customize-login-and-registration-behavior).