<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Okta Developer</title>
    <description>Secure, scalable, and highly available authentication and user management for any app.
</description>
    <link>https://developer.okta.com</link>
    <atom:link href="https://developer.okta.com/feed.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title>Build a Secure TypeScript MCP Client with Cross App Access (XAA)</title>
        <description>&lt;p&gt;Enterprise apps rarely work alone. Imagine this scenario: HR wants to ensure that a new employee completes their new hire onboarding, and they want this verification in the company’s HR app so it’s tied to the employee’s personnel file. But the onboarding tasks come from a task-tracking tool with requirements defined by each department, from a different vendor and on a different domain than the HR app. It’s on IT to bridge the two.&lt;/p&gt;

&lt;p&gt;Wiring the two together normally costs the user an OAuth consent screen or costs IT a shared service account. Cross App Access (XAA) removes that step. The company’s identity provider vouches for the user across the boundary, under a policy the admin sets in advance. In this tutorial, you’ll build that flow end-to-end in TypeScript.&lt;/p&gt;

&lt;p&gt;You’ll build a Model Context Protocol (MCP) requesting app: a web app that signs a user in through an enterprise Identity Provider (IdP), exchanges that identity for a delegation token called an Identity Assertion JWT Authorization Grant (ID-JAG), trades the ID-JAG for an access token, and calls a protected MCP server to fetch real data. You’ll use the MCP TypeScript SDK, which added first-class Cross App Access support, and you’ll test everything against &lt;a href=&quot;https://xaa.dev&quot;&gt;xaa.dev&lt;/a&gt;, the free XAA playground from the Okta Dev Advocacy team.&lt;/p&gt;

&lt;p&gt;By the end of this post, you can:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Explain what XAA is and why AI agents and enterprise apps need it&lt;/li&gt;
  &lt;li&gt;Read an ID-JAG and understand key claims in it&lt;/li&gt;
  &lt;li&gt;Understand each step of the XAA flow through the SDK functions&lt;/li&gt;
  &lt;li&gt;Let the SDK’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAppAccessProvider&lt;/code&gt; run the whole flow for you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; 20 or later&lt;/li&gt;
  &lt;li&gt;npm, which ships with Node.js&lt;/li&gt;
  &lt;li&gt;A free &lt;a href=&quot;https://xaa.dev&quot;&gt;xaa.dev&lt;/a&gt; registration (email only)&lt;/li&gt;
  &lt;li&gt;Familiarity with OAuth 2.0 concepts. If Proof Key for Code Exchange (PKCE) is new to you, read &lt;a href=&quot;/blog/2025/07/28/express-oauth-pkce&quot;&gt;Secure Your Express App with OAuth 2.0, OIDC, and PKCE&lt;/a&gt; first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code in this tutorial runs against &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@modelcontextprotocol/client&lt;/code&gt; version &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.0.0-alpha.2&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openid-client&lt;/code&gt; 6, Express 4, and the xaa.dev playground services. The MCP SDK version is a prerelease of the v2 line, and APIs can change between prereleases, so check the sample repository for the exact dependency versions.&lt;/p&gt;

&lt;p&gt;&lt;strong class=&quot;hide&quot;&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#what-youll-build-a-typescript-mcp-requesting-app&quot; id=&quot;markdown-toc-what-youll-build-a-typescript-mcp-requesting-app&quot;&gt;What you’ll build: a TypeScript MCP requesting app&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#register-your-requesting-app-on-xaadev&quot; id=&quot;markdown-toc-register-your-requesting-app-on-xaadev&quot;&gt;Register your requesting app on xaa.dev&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#set-up-the-typescript-project&quot; id=&quot;markdown-toc-set-up-the-typescript-project&quot;&gt;Set up the TypeScript project&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#run-your-typescript-mcp-app-with-xaadev&quot; id=&quot;markdown-toc-run-your-typescript-mcp-app-with-xaadev&quot;&gt;Run your TypeScript MCP app with xaa.dev&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-four-step-xaa-flow&quot; id=&quot;markdown-toc-the-four-step-xaa-flow&quot;&gt;The four-step XAA flow&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#inspect-the-tokens-in-the-dashboard&quot; id=&quot;markdown-toc-inspect-the-tokens-in-the-dashboard&quot;&gt;Inspect the tokens in the dashboard&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#id-jag-the-token-that-carries-identity-across-apps&quot; id=&quot;markdown-toc-id-jag-the-token-that-carries-identity-across-apps&quot;&gt;ID-JAG: the token that carries identity across apps&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#why-cross-app-access-matters-for-ai-agents&quot; id=&quot;markdown-toc-why-cross-app-access-matters-for-ai-agents&quot;&gt;Why Cross App Access matters for AI agents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-model-context-protocol-and-its-typescript-sdk&quot; id=&quot;markdown-toc-the-model-context-protocol-and-its-typescript-sdk&quot;&gt;The Model Context Protocol and its TypeScript SDK&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#inside-the-mcp-ts-sdks-crossappaccess-module&quot; id=&quot;markdown-toc-inside-the-mcp-ts-sdks-crossappaccess-module&quot;&gt;Inside the MCP TS SDK’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crossAppAccess&lt;/code&gt; module&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#walk-through-the-xaa-flow-in-typescript&quot; id=&quot;markdown-toc-walk-through-the-xaa-flow-in-typescript&quot;&gt;Walk through the XAA flow in TypeScript&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#sign-the-user-in-with-openid-connect-and-pkce&quot; id=&quot;markdown-toc-sign-the-user-in-with-openid-connect-and-pkce&quot;&gt;Sign the user in with OpenID Connect and PKCE&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#exchange-the-id-token-for-an-id-jag&quot; id=&quot;markdown-toc-exchange-the-id-token-for-an-id-jag&quot;&gt;Exchange the ID token for an ID-JAG&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#exchange-the-id-jag-for-an-access-token&quot; id=&quot;markdown-toc-exchange-the-id-jag-for-an-access-token&quot;&gt;Exchange the ID-JAG for an access token&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#fetch-data-from-the-mcp-server&quot; id=&quot;markdown-toc-fetch-data-from-the-mcp-server&quot;&gt;Fetch data from the MCP server&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#crossappaccessprovider-runs-the-whole-flow&quot; id=&quot;markdown-toc-crossappaccessprovider-runs-the-whole-flow&quot;&gt;CrossAppAccessProvider runs the whole flow&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#where-xaa-fits-in-your-real-architecture&quot; id=&quot;markdown-toc-where-xaa-fits-in-your-real-architecture&quot;&gt;Where XAA fits in your real architecture&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#learn-more-about-cross-app-access-id-jag-and-mcp&quot; id=&quot;markdown-toc-learn-more-about-cross-app-access-id-jag-and-mcp&quot;&gt;Learn more about Cross App Access, ID-JAG, and MCP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-youll-build-a-typescript-mcp-requesting-app&quot;&gt;What you’ll build: a TypeScript MCP requesting app&lt;/h2&gt;

&lt;p&gt;The sample app is an employee onboarding dashboard. After a single sign-in at the playground IdP, it fetches the user’s onboarding checklist from a protected MCP server and renders progress stats, an “up next” card, and the checklist itself. A “behind the scenes” panel shows all four XAA steps executing live, with timings and expandable decoded tokens, because watching the tokens flow is the best way to learn the protocol.&lt;/p&gt;

&lt;p&gt;One naming note before the build: in this post, the MCP client and the requesting app are the same thing. The app is an MCP client toward the server it calls, and a requesting app in XAA terms, because it requests delegated access on behalf of the signed-in user.&lt;/p&gt;

&lt;p&gt;The app spans four files:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/server.ts&lt;/code&gt;: an Express server that handles the OpenID Connect sign-in with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openid-client&lt;/code&gt; and streams the flow steps to the browser over server-sent events (SSE)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/xaa.ts&lt;/code&gt;: the XAA flow via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAppAccessProvider&lt;/code&gt;, built on the MCP TypeScript SDK&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/config.ts&lt;/code&gt;: environment variables and app configuration&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public/index.html&lt;/code&gt;: the dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete project is on GitHub in the &lt;a href=&quot;https://github.com/oktadev/okta-xaa-typescript-mcp-sdk-example&quot;&gt;okta-xaa-typescript-mcp-sdk-example repository&lt;/a&gt;. Get it running first, then this post walks back through what each token in the chain does and how the SDK produces it.&lt;/p&gt;

&lt;h2 id=&quot;register-your-requesting-app-on-xaadev&quot;&gt;Register your requesting app on xaa.dev&lt;/h2&gt;

&lt;p&gt;Rather than stand up an enterprise IdP and a protected resource yourself, you’ll code against &lt;a href=&quot;https://xaa.dev&quot;&gt;xaa.dev&lt;/a&gt;, the free XAA playground from the Okta Dev Advocacy team. It hosts the three services you don’t have to build: the IdP (IdenX at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://idp.xaa.dev&lt;/code&gt;), the resource’s authorization server (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://auth.resource.xaa.dev&lt;/code&gt;), and a protected todo resource available as both a REST API and an MCP server (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://mcp.xaa.dev/mcp&lt;/code&gt;). You register your app once and get the credentials your project needs because XAA spans two trust domains: your app needs one identity at the IdP to sign users in and another at the resource’s authorization server to get access tokens. For a tour of the playground itself, read &lt;a href=&quot;/blog/2026/01/20/xaa-dev-playground&quot;&gt;Introducing xaa.dev: A Playground for Cross App Access&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Go to the &lt;a href=&quot;https://xaa.dev/developer/register&quot;&gt;requesting app registration page&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Enter your email address. It scopes which registered apps are visible to you; xaa.dev creates no account and sends no email.&lt;/li&gt;
  &lt;li&gt;Select &lt;strong&gt;+ Register New App&lt;/strong&gt; and fill in the form:
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Application Name&lt;/strong&gt;: any label, for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Onboarding App - Local Dev&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Redirect URIs&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:3001/callback&lt;/code&gt; (the match is exact, including scheme, host, port, and path)&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Connect to Resource&lt;/strong&gt;: select the &lt;strong&gt;Todo MCP server&lt;/strong&gt; resource (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;todo0-mcp&lt;/code&gt;) and keep the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;todos.read&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcp.access&lt;/code&gt; scopes&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Save the credentials from the confirmation modal&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Registration gives you at least two sets of credentials, and mixing them up is the most common XAA mistake:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Client&lt;/th&gt;
      &lt;th&gt;Credentials&lt;/th&gt;
      &lt;th&gt;Identifies your app at&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Main client&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_id&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_secret&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;the IdP, for sign-in and for the token exchange&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Resource client&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource_client_id&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource_client_secret&lt;/code&gt; (the ID looks like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_xxx-at-todo0-mcp&lt;/code&gt;)&lt;/td&gt;
      &lt;td&gt;the resource’s authorization server&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Why separate credentials? The IdP and the resource’s authorization server are separate trust domains, so your app holds a separate identity at each. Using the main client’s credentials at the resource’s authorization server results in an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalid_client&lt;/code&gt; error.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Some registrations also show a distinct pair for the token exchange itself. If yours does, keep it aside; the sample has a slot for it, and reuses the main client when you leave that slot empty.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;set-up-the-typescript-project&quot;&gt;Set up the TypeScript project&lt;/h2&gt;

&lt;p&gt;Clone the sample and install the dependencies:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/oktadev/okta-xaa-typescript-mcp-sdk-example.git
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;okta-xaa-typescript-mcp-sdk-example
npm &lt;span class=&quot;nb&quot;&gt;install
cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Fill in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; with the credentials from your registration:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Main client: identifies your app at the IdP&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;XAA_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;YOUR_CLIENT_ID
&lt;span class=&quot;nv&quot;&gt;XAA_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;YOUR_CLIENT_SECRET

&lt;span class=&quot;c&quot;&gt;# Token-exchange client, if your registration shows a separate pair for it.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Leave both blank to reuse the main client above.&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;EXCHANGE_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;EXCHANGE_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Resource client: identifies your app at the resource's authorization server&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;MCP_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;YOUR_RESOURCE_CLIENT_ID
&lt;span class=&quot;nv&quot;&gt;MCP_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;YOUR_RESOURCE_CLIENT_SECRET

&lt;span class=&quot;c&quot;&gt;# xaa.dev endpoints (no trailing slashes)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;IDP_BASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;https://idp.xaa.dev
&lt;span class=&quot;nv&quot;&gt;AUTH_SERVER_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;https://auth.resource.xaa.dev
&lt;span class=&quot;nv&quot;&gt;MCP_SERVER_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;https://mcp.xaa.dev/mcp

&lt;span class=&quot;nv&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3001
&lt;span class=&quot;nv&quot;&gt;BASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;http://localhost:3001
&lt;span class=&quot;nv&quot;&gt;XAA_SCOPE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;todos.read mcp.access
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Keep the URLs free of trailing slashes. The IdP compares the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;audience&lt;/code&gt; value as an exact string, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://auth.resource.xaa.dev/&lt;/code&gt; with a slash fails where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://auth.resource.xaa.dev&lt;/code&gt; succeeds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The sample’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt; excludes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt;. Never commit client secrets, ID tokens, ID-JAGs, or access tokens to version control.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;run-your-typescript-mcp-app-with-xaadev&quot;&gt;Run your TypeScript MCP app with xaa.dev&lt;/h2&gt;

&lt;p&gt;Start the server and open the dashboard:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:3001&lt;/code&gt; and select &lt;strong&gt;Sign in with company SSO&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/typescript-mcp-sdk-cross-app-access/app-login-screen-8d373f1134ccd0edeb6f6b8a77f831ef26a998ae7fceb04998dfee51cc5392be.jpg&quot; alt=&quot;Employee Onboarding app welcome screen secured by Cross App Access, with a Sign in with company SSO button and enterprise identity provided by IdenX&quot; width=&quot;700&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;IdenX accepts any email address, so no real credentials are involved. It then shows a &lt;strong&gt;Verify Your Identity&lt;/strong&gt; screen asking for a verification code. The playground runs in demo mode and sends no email, so enter any six digits.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/typescript-mcp-sdk-cross-app-access/verify-identity-screen-b18b56679a532a663efef035decaeb5d93f0a0937e5ae402ee69abe724a8002b.jpg&quot; alt=&quot;IdenX Verify Your Identity screen with a six-digit verification code field and a demo mode notice explaining that no email is sent and any six digits work&quot; width=&quot;600&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After sign-in, the app runs the flow automatically: four steps light up in order in the “behind the scenes” panel with real timings, and the onboarding checklist renders as soon as the last one delivers the data.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/typescript-mcp-sdk-cross-app-access/app-dashboard-b4b7d33b9a1babf8015d41bd90ea79a9c34233c8ca543e72b5652b6556988bfc.jpg&quot; alt=&quot;Employee onboarding dashboard showing a completed four-step Cross App Access flow with per-step timings, the issued access token, decoded token claims, and a to-do checklist fetched from the MCP server&quot; width=&quot;1200&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You never saw a consent screen, and your app never held a credential for the todo service. That’s the point of XAA, and the panel shows how it happened.&lt;/p&gt;

&lt;h2 id=&quot;the-four-step-xaa-flow&quot;&gt;The four-step XAA flow&lt;/h2&gt;

&lt;p&gt;The panel’s four cards are the whole protocol. Steps 1, 3, and 4 are standard OAuth 2.0; step 2 is the exchange XAA adds.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Step 1: User signs in (OpenID Connect + PKCE)
  Browser -&amp;gt; IdP                            -&amp;gt; your app gets an ID token

Step 2: Token exchange (RFC 8693)
  Your app -&amp;gt; IdP token endpoint            -&amp;gt; ID-JAG

Step 3: JWT bearer grant (RFC 7523)
  Your app -&amp;gt; resource authorization server -&amp;gt; access token

Step 4: Call the resource (RFC 6750)
  Your app -&amp;gt; MCP server (Bearer token)     -&amp;gt; data
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The user authenticates once with the IdP, and your app receives an ID token. Your app exchanges that ID token at the IdP for an ID-JAG using &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8693&quot;&gt;OAuth 2.0 Token Exchange&lt;/a&gt; (Request for Comments (RFC) 8693). Your app presents the ID-JAG to the resource’s authorization server using the &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7523&quot;&gt;JWT bearer grant&lt;/a&gt; (RFC 7523) and receives a scoped access token. Finally, your app calls the protected resource with that token as a standard &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6750&quot;&gt;Bearer credential&lt;/a&gt; (RFC 6750).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/typescript-mcp-sdk-cross-app-access/xaa-flow-diagram-4d32364bb1fbf190cf1d770039a8c24c275948d96530eb0420e8afdb18aab6cd.svg&quot; alt=&quot;The Cross App Access flow in the TypeScript MCP app, in which the user, web app, IdP, authorization server, and MCP server exchange an ID token, an ID-JAG, and a Bearer access token across four steps&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;inspect-the-tokens-in-the-dashboard&quot;&gt;Inspect the tokens in the dashboard&lt;/h2&gt;

&lt;p&gt;Select any step card to expand its decoded token. The next section walks through what the ID-JAG’s claims mean; for now, the one thing worth confirming is that the access token’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt; matches the ID-JAG’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource&lt;/code&gt;, byte for byte. That match is the delegation chain holding together, and a mismatch is the most common cause of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;401&lt;/code&gt; from the resource server.&lt;/p&gt;

&lt;p&gt;Below the flow steps, the &lt;strong&gt;ACCESS TOKEN&lt;/strong&gt; card displays the Bearer token issued in step 3, with an expand toggle to inspect the full JWT. The &lt;strong&gt;TOKEN CLAIMS&lt;/strong&gt; card decodes the same token and surfaces &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope&lt;/code&gt;. Together, they confirm the right issuer signed the token, it targets the MCP server, it carries the user’s identity, and the authorization server granted the scopes you asked for.&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;🔄 Re-run (SDK discovers the auth server)&lt;/strong&gt; at any time to replay the flow and watch &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAppAccessProvider&lt;/code&gt; handle discovery automatically.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The dashboard’s token inspector exists for learning and local debugging. Keep raw tokens out of production interfaces and logs; when troubleshooting in production, log redacted identifiers and non-sensitive claims instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This app is read-only by design. It requests &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;todos.read&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcp.access&lt;/code&gt;, and nothing else. Least privilege applies to AI agents and requesting apps the same way it applies to users.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;id-jag-the-token-that-carries-identity-across-apps&quot;&gt;ID-JAG: the token that carries identity across apps&lt;/h2&gt;

&lt;p&gt;The ID-JAG is a new token type introduced by XAA. It’s a signed JSON Web Token (JWT) that the IdP issues when your app exchanges the user’s ID token for it. Think of it as a sealed envelope from the IdP that says: “This app acts for this user, toward this specific resource, with these scopes, for the next five minutes.”&lt;/p&gt;

&lt;p&gt;Here is a decoded ID-JAG from the app you just ran:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;iss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://idp.xaa.dev&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sub&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;usr_1a2b3c4d5e6f&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;aud&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://auth.resource.xaa.dev&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;resource&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://mcp.xaa.dev/mcp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;client_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;client_1a2b3c4d-at-todo0-mcp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;scope&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;todos.read mcp.access&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;user@example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;jti&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;69586a9a-b962-4f5d-971f-40f12173bcf2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;iat&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1783333416&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;exp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1783333716&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Most of those claims are standard JWT fields. Three decide whether your integration works:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt; names the token’s consumer: the authorization server that validates this ID-JAG. It isn’t the resource itself.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource&lt;/code&gt; names the target API. The authorization server copies this value into the access token’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt; claim, so whatever you put here is what the resource validates later.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_id&lt;/code&gt; is your app’s identity at the resource’s authorization server, not at the IdP. That’s the two-client model from registration, showing up in the token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource&lt;/code&gt; backwards and the exchange either fails outright or succeeds into a token the resource server rejects, which is the error that costs the most debugging time.&lt;/p&gt;

&lt;p&gt;The JWT header also carries &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;typ&quot;: &quot;oauth-id-jag+jwt&quot;&lt;/code&gt;, and authorization servers reject anything else. That prevents attackers from replaying other JWTs, such as ID tokens, as authorization grants. The five-minute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exp&lt;/code&gt; window and the one-time &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jti&lt;/code&gt; close off replay from the other direction.&lt;/p&gt;

&lt;h2 id=&quot;why-cross-app-access-matters-for-ai-agents&quot;&gt;Why Cross App Access matters for AI agents&lt;/h2&gt;

&lt;p&gt;You just watched one user action cross one app boundary. Enterprise software rarely stops at one. A single action fans out across many systems, and AI agents make the fan-out constant: an assistant reads a document, files a ticket, checks a calendar, and logs an audit event, all on behalf of one person.&lt;/p&gt;

&lt;p&gt;Each of those hops needs the user’s identity. The traditional answer is a consent screen at every boundary. Users click through prompts they don’t read, and IT teams lose visibility into which app talks to which other app, because the authorization rests on personal consent. Some teams give up and share a service account, which puts a single overprivileged credential in front of everyone’s data.&lt;/p&gt;

&lt;p&gt;XAA replaces that consent step with a policy the admin configures in advance. Your enterprise IdP already knows who the user is, because the user signed in this morning, so it vouches for that identity across the boundary with a short-lived signed token. The admin decides which app connects to which resource, with which scopes. The user signs in once, and every hop after that is an auditable cryptographic handoff.&lt;/p&gt;

&lt;p&gt;Cross App Access is the industry term for a pattern built on the &lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/&quot;&gt;Identity Assertion Authorization Grant&lt;/a&gt; specification, an active Internet-Draft at the Internet Engineering Task Force (IETF) that defines the ID-JAG token and the exchange flow that XAA uses. To go deeper on the protocol itself, read &lt;a href=&quot;/blog/2025/09/03/cross-app-access&quot;&gt;Build Secure Agent-to-App Connections with Cross App Access (XAA)&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-model-context-protocol-and-its-typescript-sdk&quot;&gt;The Model Context Protocol and its TypeScript SDK&lt;/h2&gt;

&lt;p&gt;Before diving into code, a quick word on the other protocol in this tutorial. The &lt;a href=&quot;https://modelcontextprotocol.io/&quot;&gt;Model Context Protocol&lt;/a&gt; is an open standard created by Anthropic that continues to evolve, providing AI applications with a common way to connect to tools and data. An MCP server exposes capabilities (tools to call, resources to read, prompts to use), and an MCP client connects to those servers over a standard transport. Instead of building one custom integration per data source, build to one protocol so any MCP-capable AI application can use it. The &lt;a href=&quot;https://github.com/modelcontextprotocol/typescript-sdk&quot;&gt;MCP TypeScript SDK&lt;/a&gt; is the official implementation for JavaScript and TypeScript developers, handling protocol details such as transports, the initialization handshake, message schemas, and authorization.&lt;/p&gt;

&lt;p&gt;The MCP community evolves the specification through Specification Enhancement Proposals (SEPs). Cross App Access support entered the protocol as &lt;a href=&quot;https://github.com/modelcontextprotocol/ext-auth&quot;&gt;Specification Enhancement Proposal (SEP) 990, Enterprise Managed Authorization&lt;/a&gt;, and the TypeScript SDK ships the implementation in its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crossAppAccess&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;With SEP-990 implemented in the SDK, an XAA-enabled MCP client differs from a plain one by a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authProvider&lt;/code&gt; option and one callback, plus any compatibility adjustments your authorization server needs. The protocol work (discovery, token exchange, the JWT bearer grant, and retries) lives in the SDK rather than in your app.&lt;/p&gt;

&lt;h2 id=&quot;inside-the-mcp-ts-sdks-crossappaccess-module&quot;&gt;Inside the MCP TS SDK’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crossAppAccess&lt;/code&gt; module&lt;/h2&gt;

&lt;p&gt;Three pieces of the TypeScript SDK matter for this tutorial:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverAndRequestJwtAuthGrant()&lt;/code&gt;&lt;/strong&gt; performs step 2. It discovers the IdP’s token endpoint from its metadata, then sends an RFC 8693 token-exchange request and returns the ID-JAG. A sibling function, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestJwtAuthorizationGrant()&lt;/code&gt;, skips discovery when you already know the token endpoint.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exchangeJwtAuthGrant()&lt;/code&gt;&lt;/strong&gt; performs step 3: given the resource authorization server’s token endpoint, it presents the ID-JAG with the RFC 7523 JWT bearer grant and returns the access token. Unlike the step 2 function, it does no discovery of its own.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAppAccessProvider&lt;/code&gt;&lt;/strong&gt; is the production path. It plugs into the SDK’s transport as an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OAuthClientProvider&lt;/code&gt; and runs steps 2 through 4 automatically: it calls the MCP server, receives a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;401&lt;/code&gt; challenge, discovers the authorization server through &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9728&quot;&gt;protected resource metadata (RFC 9728)&lt;/a&gt;, invokes your callback to obtain a fresh ID-JAG, exchanges it, and retries the request with the new access token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;discoverAndRequestJwtAuthGrant()&lt;/code&gt; in step 2 and the provider in the final section. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exchangeJwtAuthGrant()&lt;/code&gt; is the standalone way to do step 3; the sample never calls it, because the provider builds that same JWT bearer request itself.&lt;/p&gt;

&lt;h2 id=&quot;walk-through-the-xaa-flow-in-typescript&quot;&gt;Walk through the XAA flow in TypeScript&lt;/h2&gt;

&lt;p&gt;The next four sections take each step in turn, so that you can trace every token in the chain. The fifth shows &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAppAccessProvider&lt;/code&gt;, which drives steps 2 through 4 from one configuration. The code below is adapted from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/server.ts&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/xaa.ts&lt;/code&gt;, trimmed to the lines that matter for each step.&lt;/p&gt;

&lt;h3 id=&quot;sign-the-user-in-with-openid-connect-and-pkce&quot;&gt;Sign the user in with OpenID Connect and PKCE&lt;/h3&gt;

&lt;p&gt;Step 1 is a standard OpenID Connect Authorization Code flow with &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7636&quot;&gt;PKCE&lt;/a&gt;, and standard means you shouldn’t write it yourself. The sample uses &lt;a href=&quot;https://github.com/panva/openid-client&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openid-client&lt;/code&gt;&lt;/a&gt;, which handles discovery, the PKCE challenge, and the code exchange. The piece XAA cares about is the ID token in the response, because it becomes the input to step 2.&lt;/p&gt;

&lt;p&gt;Discovery returns a configuration object that the other calls take as their first argument. In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/server.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;openid-client&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;discovery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://idp.xaa.dev&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;XAA_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;XAA_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/login&lt;/code&gt; route generates a fresh PKCE verifier, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nonce&lt;/code&gt;, stores them in the session, and builds the authorization URL:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;verifier&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;randomPKCECodeVerifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pkceVerifier&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;verifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;randomState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nonce&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;randomNonce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;buildAuthorizationUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;redirect_uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;REDIRECT_URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;openid email profile&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;nonce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nonce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;code_challenge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;calculatePKCECodeChallenge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;verifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;code_challenge_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;S256&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/callback&lt;/code&gt; route then exchanges the code for tokens in one call:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tokens&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;authorizationCodeGrant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;originalUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BASE_URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;pkceCodeVerifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pkceVerifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;expectedState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;expectedNonce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nonce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;idToken&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;claims&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;claims&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That one call verifies the ID token’s signature against the IdP’s published keys, checks the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exp&lt;/code&gt; claims, and confirms the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nonce&lt;/code&gt; match what you stored. Hand-rolled sign-in code tends to decode the ID token payload without verifying any of it, which means trusting claims from a token you haven’t proven came from your IdP. Here, the ID token is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subject_token&lt;/code&gt; for step 2 and the root of the whole delegation chain, so that verification is load-bearing.&lt;/p&gt;

&lt;p&gt;The app keeps the ID token in a server-side session. It never sends the ID token to the resource server; the token’s only job now is to prove the user’s identity to the IdP during token exchange.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Production note:&lt;/strong&gt; Configure session cookies with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpOnly&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Secure&lt;/code&gt;, and an appropriate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SameSite&lt;/code&gt; value, and keep client secrets out of browser code. The sample stores sessions in memory, which is fine for a local demo and wrong for production: use a session store so tokens survive a restart and scale past one process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;exchange-the-id-token-for-an-id-jag&quot;&gt;Exchange the ID token for an ID-JAG&lt;/h3&gt;

&lt;p&gt;Step 2 is the XAA step. One SDK call performs the whole RFC 8693 token exchange:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;discoverAndRequestJwtAuthGrant&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@modelcontextprotocol/client&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;jag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;discoverAndRequestJwtAuthGrant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;idpUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://idp.xaa.dev&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;audience&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://auth.resource.xaa.dev&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// who validates the ID-JAG&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://mcp.xaa.dev/mcp&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;       &lt;span class=&quot;c1&quot;&gt;// what the access token targets&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;idToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;idToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;XAA_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;       &lt;span class=&quot;c1&quot;&gt;// main client&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;clientSecret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;XAA_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;todos.read mcp.access&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;jag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;jwtAuthGrant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// the ID-JAG (a JWT)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;jag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expiresIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;// 300 seconds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Under the hood, the function discovers the IdP’s token endpoint from its metadata, then sends a form-encoded POST with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grant_type=urn:ietf:params:oauth:grant-type:token-exchange&lt;/code&gt;, your ID token as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subject_token&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requested_token_type=urn:ietf:params:oauth:token-type:id-jag&lt;/code&gt;. The response’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt; field carries the ID-JAG. The naming feels odd, but RFC 8693 reuses the standard token response shape, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token_type&lt;/code&gt; value &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N_A&lt;/code&gt; signals that this token isn’t a bearer credential.&lt;/p&gt;

&lt;p&gt;Pay attention to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;audience&lt;/code&gt; versus &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource&lt;/code&gt;, because they answer different questions. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;audience&lt;/code&gt; names who validates this ID-JAG in step 3: the authorization server. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource&lt;/code&gt; names what your access token targets in step 4: the MCP server. The authorization server copies the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource&lt;/code&gt; verbatim into the access token’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt; claim, so an incorrect &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource&lt;/code&gt; here later surfaces as a confusing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;401&lt;/code&gt; from the resource server.&lt;/p&gt;

&lt;p&gt;One optimization worth noting: the discovery call incurs an extra network round trip on every run. If your server already fetched the IdP metadata (this app caches it during sign-in), call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestJwtAuthorizationGrant()&lt;/code&gt; with the known &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tokenEndpoint&lt;/code&gt; instead and skip rediscovery. Caching static configuration is fine; the tokens themselves are always requested live.&lt;/p&gt;

&lt;h3 id=&quot;exchange-the-id-jag-for-an-access-token&quot;&gt;Exchange the ID-JAG for an access token&lt;/h3&gt;

&lt;p&gt;In step 3, your app presents the ID-JAG to the resource’s authorization server with the RFC 7523 JWT bearer grant, authenticating with the resource client credentials. The request posts &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&lt;/code&gt; with the ID-JAG as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assertion&lt;/code&gt;, and the response is an ordinary OAuth token response: an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token_type&lt;/code&gt; of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bearer&lt;/code&gt;, and an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expires_in&lt;/code&gt; lifetime worth reading rather than assuming.&lt;/p&gt;

&lt;p&gt;You don’t write that request yourself. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CrossAppAccessProvider&lt;/code&gt; makes it, and the last section of this walkthrough shows how to configure it. Two details about this step save you real debugging time:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Client authentication method.&lt;/strong&gt; Developer-registered clients on xaa.dev use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_secret_post&lt;/code&gt;, which means credentials belong in the request body. The provider declares &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_secret_basic&lt;/code&gt; (an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization: Basic&lt;/code&gt; header) by default, so the client information has to name the method explicitly.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Send the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope&lt;/code&gt; parameter.&lt;/strong&gt; The provider only sends a scope when the authorization server’s metadata advertises one, and xaa.dev’s does not. Omit it and the authorization server issues an access token with an empty scope, after which the failure is quiet. The MCP server still completes the handshake, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resources/list&lt;/code&gt; still returns the resource names, and reading the todos still returns HTTP &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200&lt;/code&gt; with a JSON-RPC result. The rejection hides inside the resource payload: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;error&quot;:&quot;Unauthorized&quot;,&quot;message&quot;:&quot;Invalid or expired token&quot;}&lt;/code&gt;. Nothing throws, so your app parses that error object instead of a todo list and renders an empty checklist. Request the scopes you need, then verify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope&lt;/code&gt; claim in the decoded access token.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resulting access token is itself a JWT. Its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt; claim matches the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource&lt;/code&gt; you sent in step 2, its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt; identifies the user, and its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_id&lt;/code&gt; is the resource client. This is the delegation chain made visible: user identity from step 1, admin policy from the resource connection, and app identity from your registration, all cryptographically bound into one credential.&lt;/p&gt;

&lt;h3 id=&quot;fetch-data-from-the-mcp-server&quot;&gt;Fetch data from the MCP server&lt;/h3&gt;

&lt;p&gt;With a token in hand, step 4 is regular MCP SDK code. Given a connected &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client&lt;/code&gt;, which the next section builds, reading a resource takes one call:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;todo0://todos&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// A resource's contents can be text or binary, so narrow before parsing.&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;todos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;readResource()&lt;/code&gt; returns the user’s todo list as JSON. The playground’s MCP server exposes the todos as an MCP &lt;em&gt;resource&lt;/em&gt; (read-only data at a URI) rather than a &lt;em&gt;tool&lt;/em&gt;. Both primitives ride the same authenticated pipeline, so switching to a tool call is a one-line change to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client.callTool()&lt;/code&gt; when your resource server offers tools.&lt;/p&gt;

&lt;p&gt;Notice that the access token never appears in this code. You can attach it to the transport yourself through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestInit.headers&lt;/code&gt;, but the sample gives the transport an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authProvider&lt;/code&gt; instead, and the SDK fetches the token, attaches it, and refreshes it when it expires.&lt;/p&gt;

&lt;h3 id=&quot;crossappaccessprovider-runs-the-whole-flow&quot;&gt;CrossAppAccessProvider runs the whole flow&lt;/h3&gt;

&lt;p&gt;The sections above describe what each step sends and receives. Here is the code that runs all three, from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/xaa.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;CrossAppAccessProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;StreamableHTTPClientTransport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;discoverAndRequestJwtAuthGrant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;requestJwtAuthorizationGrant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@modelcontextprotocol/client&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;provider&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;CrossAppAccessProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Called when the transport needs an ID-JAG. The provider has already&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// discovered the authorization server and resource via RFC 9728.&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;assertion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// idpTokenEndpoint is the IdP token endpoint this app cached during&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// sign-in, passed in by the caller. Falls back to discovery without it.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;jagOptions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;audience&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;authorizationServerUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;+$/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resourceUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;idToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getIdTokenFromSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;XAA_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;clientSecret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;XAA_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;todos.read mcp.access&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;fetchFn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fetchFn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;jag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;idpTokenEndpoint&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;requestJwtAuthorizationGrant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;jagOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;tokenEndpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;idpTokenEndpoint&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;discoverAndRequestJwtAuthGrant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;jagOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;idpUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://idp.xaa.dev&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;jag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;jwtAuthGrant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;MCP_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;clientSecret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;MCP_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Two adjustments for xaa.dev developer clients. First, they authenticate&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// with client_secret_post, while the provider declares client_secret_basic&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// by default. Declaring the method on the client information makes the SDK&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// select the right one.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;saveClientInformation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;client_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;MCP_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;client_secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;MCP_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;token_endpoint_auth_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;client_secret_post&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Parameters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;saveClientInformation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Second, the provider only sends a scope when the server's metadata&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// advertises one, and xaa.dev's does not. Without a scope parameter, the&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// authorization server issues an empty-scope token, so add it here.&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;prepareTokenRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prepareTokenRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prepareTokenRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;prepareTokenRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;todos.read mcp.access&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;todos.read mcp.access&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;transport&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;StreamableHTTPClientTransport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://mcp.xaa.dev/mcp&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;authProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;provider&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;xaa-requesting-app-typescript&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;1.0.0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 401 -&amp;gt; discovery -&amp;gt; ID-JAG -&amp;gt; token -&amp;gt; retry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Walk through what the provider does on that one &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;connect()&lt;/code&gt; call. It sends the first request without a token and receives a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;401&lt;/code&gt; with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WWW-Authenticate&lt;/code&gt; header pointing at the server’s protected resource metadata. It fetches that metadata, learns which authorization server protects this resource, and then fetches the authorization server’s metadata to find the token endpoint. It calls your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assertion&lt;/code&gt; callback with the discovered URLs, exchanges the returned ID-JAG for an access token, stores the token, and then retries the original request. When the access token later expires, the next &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;401&lt;/code&gt; response triggers the same sequence again, without any code from you. Both adjustments in the snippet come from testing this flow against the live playground.&lt;/p&gt;

&lt;p&gt;Nothing in this snippet names &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auth.resource.xaa.dev&lt;/code&gt;. The provider discovered it, so the same client code works against any spec-compliant protected MCP server.&lt;/p&gt;

&lt;h2 id=&quot;where-xaa-fits-in-your-real-architecture&quot;&gt;Where XAA fits in your real architecture&lt;/h2&gt;

&lt;p&gt;The playground stands in for real systems, and the mapping is direct. IdenX stands in for your production IdP. Okta offers Cross App Access as an Early Access feature, and the token exchange in this tutorial works the same way against an Okta org once you enable the feature; check the &lt;a href=&quot;https://help.okta.com/oie/en-us/content/topics/apps/apps-cross-app-access.htm&quot;&gt;Okta Cross App Access documentation&lt;/a&gt; for current availability. To try it against a real org, &lt;a href=&quot;https://developer.okta.com/signup/&quot;&gt;sign up for an Okta Integrator Free Plan&lt;/a&gt;. The todo MCP server serves as an enterprise resource you can make available to agents and apps. Your requesting app serves as the AI agent or SaaS integration acting on the user’s behalf.&lt;/p&gt;

&lt;p&gt;The read-only scope in this tutorial is a deliberate starting point, not a limitation of the protocol. Scopes are strings that your resource server defines and an admin grants. When you’re ready to build the other side of the boundary, the resource app guides show how to validate ID-JAGs and issue access tokens from your own authorization server, whether your app federates with &lt;a href=&quot;/blog/2026/08/24/xaa-oidc-resource&quot;&gt;OpenID Connect (OIDC)&lt;/a&gt; or &lt;a href=&quot;/blog/2026/07/03/cross-app-access-saml&quot;&gt;Security Assertion Markup Language (SAML)&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;learn-more-about-cross-app-access-id-jag-and-mcp&quot;&gt;Learn more about Cross App Access, ID-JAG, and MCP&lt;/h2&gt;

&lt;p&gt;The complete source for this tutorial is in the &lt;a href=&quot;https://github.com/oktadev/okta-xaa-typescript-mcp-sdk-example&quot;&gt;okta-xaa-typescript-mcp-sdk-example repository&lt;/a&gt;. To keep exploring:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/08/21/xaa-oidc-requesting&quot;&gt;Add Cross App Access to Your OIDC Requesting Application&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/&quot;&gt;Identity Assertion Authorization Grant draft at the IETF&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8693&quot;&gt;OAuth 2.0 Token Exchange (RFC 8693)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7523&quot;&gt;JWT bearer grant (RFC 7523)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9728&quot;&gt;OAuth 2.0 Protected Resource Metadata (RFC 9728)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://modelcontextprotocol.io/&quot;&gt;Model Context Protocol documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember to follow us on &lt;a href=&quot;https://www.linkedin.com/company/oktadev&quot;&gt;LinkedIn&lt;/a&gt;, &lt;a href=&quot;https://x.com/oktadev&quot;&gt;X&lt;/a&gt;, and subscribe to our &lt;a href=&quot;https://www.youtube.com/c/OktaDev/&quot;&gt;YouTube channel&lt;/a&gt; for more exciting content. We also want to hear from you about the topics you’d like to see and any questions you may have. Leave us a comment below!&lt;/p&gt;
</description>
        <pubDate>Fri, 09 Oct 2026 00:00:00 -0500</pubDate>
        <link>https://developer.okta.com/blog/2026/10/09/typescript-mcp-sdk-cross-app-access</link>
        <guid isPermaLink="true">https://developer.okta.com/blog/2026/10/09/typescript-mcp-sdk-cross-app-access</guid>
      </item>
    
      <item>
        <title>Classic Engine Feature EOL: SSR, Desktop SSO, Device Trust</title>
        <description>&lt;p&gt;Starting March 5, 2027, Okta ends support for a few capabilities on Classic Engine. Identity and security requirements continue to evolve. Organizations expect authentication experiences that are more secure, flexible, and adaptable to changing users, devices, and applications. Okta invests in Okta Identity Engine, the modern foundation for passwordless authentication, device assurance, phishing-resistant multifactor authentication, and adaptive security policies.&lt;/p&gt;

&lt;p&gt;By moving to Okta Identity Engine, you access an environment aligned with modern security practices that is stronger, more capable, and built to support how you protect access.&lt;/p&gt;

&lt;h2 id=&quot;whats-changing-in-okta-classic-engine&quot;&gt;What’s changing in Okta Classic Engine&lt;/h2&gt;

&lt;p&gt;Here’s the full list of affected capabilities:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Self-Service Registration&lt;/li&gt;
  &lt;li&gt;Desktop Single Sign-On (Integrated Windows Authentication, or IWA)&lt;/li&gt;
  &lt;li&gt;Device Trust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once support ends, Okta turns off these capabilities on Classic Engine, and you lose access to them unless you upgrade to Okta Identity Engine first. Okta no longer addresses bugs, security vulnerabilities, or broken flows associated with them after that date. Each capability moves to a modern, more secure approach within Identity Engine, where it continues to benefit from ongoing innovation.&lt;/p&gt;

&lt;h2 id=&quot;why-upgrade-to-okta-identity-engine&quot;&gt;Why upgrade to Okta Identity Engine&lt;/h2&gt;

&lt;p&gt;Identity Engine gives you authentication capabilities Classic Engine doesn’t have, including passwordless authentication, device assurance, and a modernized policy framework built on a newer authentication pipeline. The upgrade itself is free, you get all these capabilities at no additional licensing cost as part of your existing Okta subscription.&lt;/p&gt;

&lt;p&gt;Identity Engine accelerates five core use cases:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Passwordless&lt;/strong&gt;: Remove password from the sign-in flow entirely, instead of layering a second factor on top of it&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Phishing-resistant&lt;/strong&gt;: Close every route to the account, not just the front door. An account is only as phishing-resistant as its weakest enrollment and recovery path.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;ID verification&lt;/strong&gt; : Confirm that employees and customers are who they claim to be before you issue a credential or reset one&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Zero trust&lt;/strong&gt;: Identity and access management that goes beyond what &lt;a href=&quot;https://pages.nist.gov/800-63-3/sp800-63b.html&quot;&gt;NIST Authenticator Assurance Level (AAL) guidelines&lt;/a&gt; recommends&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Device assurance&lt;/strong&gt;: Fine-grained controls that check device posture as a condition of access&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;okta-identity-engine-elevates-identity-capabilities&quot;&gt;Okta Identity Engine elevates identity capabilities&lt;/h2&gt;

&lt;p&gt;Self-Service Registration allows end users to create their own accounts without administrator involvement. In Identity Engine, you use the Profile Enrollment Policy, which provides the same user self-service experience while enhancing security and modern account management.&lt;/p&gt;

&lt;p&gt;Desktop Single Sign-On (SSO), specifically IWA, provides silent desktop authentication through on-premises infrastructure. Migrate to Agentless Desktop Single Sign-On (ADSSO) for modern desktop authentication without agents, or to Okta FastPass for passwordless platform authentication. Consult your Okta account team to determine which option fits your authentication and device management strategy.&lt;/p&gt;

&lt;p&gt;Device Trust restricts app access to managed devices through Workspace ONE. In Identity Engine, migrate to management attestation with Okta Verify instead, which verifies that Okta Verify manages and trusts the mobile device before allowing access, providing enhanced device assurance beyond the previous Device Trust feature.&lt;/p&gt;

&lt;p&gt;For step-by-step guidance on replacing these Classic Engine auth flows, see &lt;a href=&quot;https://developer.okta.com/docs/journeys/OCI-replace-ce-auth-flows/main/&quot;&gt;Replace Classic Engine authentication flows with Okta Identity Engine&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;planning-your-migration-to-okta-identity-engine&quot;&gt;Planning your migration to Okta Identity Engine&lt;/h2&gt;

&lt;p&gt;Every organization’s environment differs, so your timeline and steps depend on your current configuration and feature usage. Reach out to your account executive for help and clarification on scheduling your upgrade.&lt;/p&gt;

&lt;h2 id=&quot;key-timeline-for-classic-engine-capabilities-sunsetting&quot;&gt;Key timeline for Classic Engine capabilities sunsetting&lt;/h2&gt;

&lt;p&gt;Self-Service Registration, Desktop SSO (IWA), and Mobile Device Trust sunset on Classic Engine according to the timeline below.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Milestone&lt;/th&gt;
      &lt;th&gt;Date(s)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Initial notice&lt;/td&gt;
      &lt;td&gt;August 5, 2026&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Reminders&lt;/td&gt;
      &lt;td&gt;September 7, October 7, and November 9, 2026; January 11, February 15, and March 1, 2027&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;End of support&lt;/td&gt;
      &lt;td&gt;March 5, 2027&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Mark your calendar: March 5, 2027, is the hard deadline. Upgrade before then to avoid losing access to Self-Service Registration, Desktop SSO (IWA), and Device Trust on Classic Engine.&lt;/p&gt;

&lt;h2 id=&quot;best-practices-after-your-okta-identity-engine-upgrade&quot;&gt;Best practices after your Okta Identity Engine upgrade&lt;/h2&gt;

&lt;p&gt;Not every Classic Engine feature carries over to Identity Engine. Identity Engine fully supports most features, and in most cases, a comparable feature is available. Your Okta field and upgrade teams tell you which features are unsupported and provide steps to migrate to the replacement feature.&lt;/p&gt;

&lt;p&gt;If you have questions about any part of this transition, contact Okta Support or your account team.&lt;/p&gt;

&lt;h2 id=&quot;plan-your-migration-now&quot;&gt;Plan your migration now!&lt;/h2&gt;

&lt;p&gt;Schedule testing in a non-production environment before planning your production upgrade during an appropriate maintenance window. Contact Okta Support or your account team with any questions. You have until March 5, 2027, to complete your upgrade, so start your assessment now to make sure you land on the best upgrade window for your organization.&lt;/p&gt;

&lt;h2 id=&quot;resources-to-aid-your-okta-identity-engine-migration&quot;&gt;Resources to aid your Okta Identity Engine migration&lt;/h2&gt;

&lt;p&gt;If you’d like to learn more about identity and the concepts covered in this post, explore these official Okta resources:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/07/07/okta-journeys-for-developers&quot;&gt;Introducing Okta Journeys: A Better Way for Developers to Learn Identity&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.okta.com/docs/journeys/OCI-replace-ce-auth-flows/main/&quot;&gt;Replace Classic Engine authentication flows with Okta Identity Engine&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2025/08/06/authentication-identity-security&quot;&gt;It’s Time to Evolve Authentication Security&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember to follow us on &lt;a href=&quot;https://www.linkedin.com/company/oktadev&quot;&gt;LinkedIn&lt;/a&gt; and subscribe to our &lt;a href=&quot;https://www.youtube.com/c/oktadev&quot;&gt;YouTube&lt;/a&gt; for more exciting content. Let us know how your Identity Engine upgrade goes in the comments below, we’d love to hear about it.&lt;/p&gt;
</description>
        <pubDate>Fri, 18 Sep 2026 00:00:00 -0500</pubDate>
        <link>https://developer.okta.com/blog/2026/09/18/okta-classic-engine-deprecation</link>
        <guid isPermaLink="true">https://developer.okta.com/blog/2026/09/18/okta-classic-engine-deprecation</guid>
      </item>
    
      <item>
        <title>Supercharge Auth with Signals and the New Okta Angular SDK</title>
        <description>&lt;p&gt;Have you noticed that the Okta Angular SDK went fully standalone? There’s no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NgModule&lt;/code&gt; left to import, no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;importProvidersFrom&lt;/code&gt; wrapper to remember, and the guards are plain functions now. If you’ve been waiting for the SDK to look like the rest of your standalone Angular app, this is the release you were waiting for.&lt;/p&gt;

&lt;p&gt;In this post, we’ll pick up a small Angular v22 project and finish it. We’ll make the following changes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Add authentication using the new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provideOktaAuth&lt;/code&gt; provider function&lt;/li&gt;
  &lt;li&gt;Protect a route with the SDK’s functional guard&lt;/li&gt;
  &lt;li&gt;Load each user’s groups with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rxResource&lt;/code&gt; and a signal input&lt;/li&gt;
  &lt;li&gt;Move the Okta configuration to runtime loading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re calling Okta’s APIs directly for this project, so we don’t have to stand up a separate backend. If you want to jump to the completed project, you can find it in the &lt;a href=&quot;https://github.com/oktadev/okta-angular-auth-signals-example&quot;&gt;okta-angular-auth-signals-example&lt;/a&gt; GitHub repository. Otherwise, warm up your fingers and let’s get coding!&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;This post is best for developers familiar with Angular. If you are an Angular newbie, start by building &lt;a href=&quot;https://angular.dev/tutorials/learn-angular&quot;&gt;your first Angular app&lt;/a&gt; using the tutorial created by the Angular team.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this tutorial, you will need the following tools:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://nodejs.org/en&quot;&gt;Node.js&lt;/a&gt; v22 or greater&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://angular.dev/tools/cli&quot;&gt;Angular CLI&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;An &lt;a href=&quot;https://developer.okta.com/signup/&quot;&gt;Okta Integrator Free Plan account&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;A web browser with good debugging capabilities&lt;/li&gt;
  &lt;li&gt;Your favorite IDE&lt;/li&gt;
  &lt;li&gt;Terminal window (if you aren’t using an IDE with a built-in terminal)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong class=&quot;hide&quot;&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#get-the-starting-angular-project&quot; id=&quot;markdown-toc-get-the-starting-angular-project&quot;&gt;Get the starting Angular project&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#secure-the-angular-app-with-oauth-21-and-openid-connect-oidc-using-okta&quot; id=&quot;markdown-toc-secure-the-angular-app-with-oauth-21-and-openid-connect-oidc-using-okta&quot;&gt;Secure the Angular app with OAuth 2.1 and OpenID Connect (OIDC) using Okta&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#grant-api-scopes-to-read-users-and-groups&quot; id=&quot;markdown-toc-grant-api-scopes-to-read-users-and-groups&quot;&gt;Grant API scopes to read users and groups&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#allow-cross-origin-requests-and-protect-the-access-token&quot; id=&quot;markdown-toc-allow-cross-origin-requests-and-protect-the-access-token&quot;&gt;Allow cross-origin requests and protect the access token&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#add-authentication-with-the-standalone-okta-angular-provider&quot; id=&quot;markdown-toc-add-authentication-with-the-standalone-okta-angular-provider&quot;&gt;Add authentication with the standalone Okta Angular provider&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#protect-routes-with-functional-route-guards&quot; id=&quot;markdown-toc-protect-routes-with-functional-route-guards&quot;&gt;Protect routes with functional route guards&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#add-users-to-your-okta-org&quot; id=&quot;markdown-toc-add-users-to-your-okta-org&quot;&gt;Add users to your Okta org&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#display-users-and-review-angulars-rxresource-api&quot; id=&quot;markdown-toc-display-users-and-review-angulars-rxresource-api&quot;&gt;Display users and review Angular’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rxResource&lt;/code&gt; API&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#load-a-users-groups-with-rxresource-and-a-signal-input&quot; id=&quot;markdown-toc-load-a-users-groups-with-rxresource-and-a-signal-input&quot;&gt;Load a user’s groups with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rxResource&lt;/code&gt; and a signal input&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#display-group-details-using-signal-inputs&quot; id=&quot;markdown-toc-display-group-details-using-signal-inputs&quot;&gt;Display group details using signal inputs&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#load-your-okta-configuration-at-runtime&quot; id=&quot;markdown-toc-load-your-okta-configuration-at-runtime&quot;&gt;Load your Okta configuration at runtime&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#learn-more-about-angular-signals-standalone-apis-and-oidc&quot; id=&quot;markdown-toc-learn-more-about-angular-signals-standalone-apis-and-oidc&quot;&gt;Learn more about Angular signals, standalone APIs, and OIDC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;get-the-starting-angular-project&quot;&gt;Get the starting Angular project&lt;/h2&gt;

&lt;p&gt;We’re starting from a project instead of building everything from scratch, because a pile of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ng generate&lt;/code&gt; commands would distract us from the exciting coding parts. Open a terminal window and run the following commands to get a local copy of the starter and install dependencies. Feel free to fork the repo so you can track your changes.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt; starter https://github.com/oktadev/okta-angular-auth-signals-example.git
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;okta-angular-auth-signals-example
npm ci
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Open the project in your IDE and take a look around. I already added &lt;a href=&quot;https://www.npmjs.com/package/@okta/okta-angular&quot;&gt;Okta Angular&lt;/a&gt; and &lt;a href=&quot;https://www.npmjs.com/package/@okta/okta-auth-js&quot;&gt;Okta Auth JS&lt;/a&gt; to the project, along with a few pieces you’d otherwise spend the afternoon typing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/okta.ts&lt;/code&gt; is a service that calls Okta’s user management API. It uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Service()&lt;/code&gt; decorator to declare a singleton service.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/okta.types.ts&lt;/code&gt; holds the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OktaUser&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OktaGroup&lt;/code&gt; interfaces&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/okta-auth.interceptor.ts&lt;/code&gt; attaches the access token and a proof of possession to Okta requests, and only to Okta requests&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/app.routes.ts&lt;/code&gt; has the routes, including the SDK’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OktaCallbackComponent&lt;/code&gt; on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;login/callback&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don’t run the app yet. It won’t start, and that’s on purpose – there’s no authentication configuration in the project. The service and the interceptor both ask Angular for an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OktaAuth&lt;/code&gt; instance, and nothing provides one yet. That’s the first thing you’ll fix.&lt;/p&gt;

&lt;h2 id=&quot;secure-the-angular-app-with-oauth-21-and-openid-connect-oidc-using-okta&quot;&gt;Secure the Angular app with OAuth 2.1 and OpenID Connect (OIDC) using Okta&lt;/h2&gt;

&lt;p&gt;You’ll use Okta to handle authentication and authorization for your Angular application securely.&lt;/p&gt;

&lt;p&gt;Before you begin, you’ll need an Okta Integrator Free Plan account. To get one, sign up for an &lt;a href=&quot;https://developer.okta.com/login&quot;&gt;Integrator account&lt;/a&gt;. Once you have an account, sign in to your &lt;a href=&quot;https://developer.okta.com/login&quot;&gt;Integrator account&lt;/a&gt;. Next, in the Admin Console:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Go to &lt;strong&gt;Applications and Resources&lt;/strong&gt; &amp;gt; &lt;strong&gt;Applications&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Click &lt;strong&gt;Create App Integration&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;In the &lt;strong&gt;Sign in Method&lt;/strong&gt; section, select &lt;strong&gt;OIDC - OpenID Connect&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Select &lt;strong&gt;Single-Page Application&lt;/strong&gt; as the application type, then click &lt;strong&gt;Next&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Enter an app integration name&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;In the &lt;strong&gt;Grant type&lt;/strong&gt; section, ensure that both &lt;strong&gt;Authorization Code&lt;/strong&gt; and &lt;strong&gt;Refresh Token&lt;/strong&gt; are selected&lt;/li&gt;
  &lt;li&gt;Configure the redirect URIs:
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Sign-in redirect URIs&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:4200/login/callback&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Sign-out redirect URIs&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:4200&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;In the &lt;strong&gt;Assignments&lt;/strong&gt; section, assign a group or keep the default &lt;strong&gt;Everyone&lt;/strong&gt; group assigned for all&lt;/li&gt;
  &lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;details&gt;
  &lt;summary&gt;Where are my new app's credentials?&lt;/summary&gt;

&lt;p&gt;Creating an OIDC Single-Page App manually in the Admin Console configures your Okta Org with the application settings.  You may also need to configure trusted origins for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:4200&lt;/code&gt; in &lt;strong&gt;Security&lt;/strong&gt; &amp;gt; &lt;strong&gt;API&lt;/strong&gt; &amp;gt; &lt;strong&gt;Trusted Origins&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After creating the app, you can find the configuration details on the app’s &lt;strong&gt;General&lt;/strong&gt; tab:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Client ID&lt;/strong&gt;: Found in the &lt;strong&gt;Client Credentials&lt;/strong&gt; section&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Issuer&lt;/strong&gt;: Found in the &lt;strong&gt;Issuer URI&lt;/strong&gt; field for the authorization server that appears by selecting &lt;strong&gt;Security&lt;/strong&gt; &amp;gt; &lt;strong&gt;API&lt;/strong&gt; from the navigation pane.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Issuer:    https://dev-133337.okta.com/oauth2/default
Client ID: 0oab8eb55Kb9jdMIr5d6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You can also use the &lt;a href=&quot;https://github.com/okta/okta-cli-client&quot;&gt;Okta CLI Client&lt;/a&gt; or &lt;a href=&quot;https://github.com/okta/okta-powershell-cli&quot;&gt;Okta PowerShell Module&lt;/a&gt; to automate this process. See &lt;a href=&quot;https://developer.okta.com/docs/guides/sign-into-spa/-/create-okta-application/&quot;&gt;this guide&lt;/a&gt; for more information about setting up your app.&lt;/p&gt;

&lt;/details&gt;

&lt;p&gt;Note the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client ID&lt;/code&gt; and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Issuer&lt;/code&gt;. You’ll need those values in a moment.&lt;/p&gt;

&lt;p&gt;One important detail about that issuer. The credentials example above shows an issuer ending in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/oauth2/default&lt;/code&gt;, which is the custom authorization server Okta creates for you. This project uses the &lt;strong&gt;org&lt;/strong&gt; authorization server instead, so your issuer is your Okta domain with nothing after it, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;<span class="okta-preview-domain">https://{yourOktaDomain}</span>&lt;/code&gt;. We need the org authorization server because it issues access tokens that the Okta management APIs accept, and reading users and groups is the entire point of this app.&lt;/p&gt;

&lt;p&gt;In your own applications, you’ll most likely use a custom authorization server for your APIs, since that’s where you control scopes, claims, and access policies. Read &lt;a href=&quot;https://developer.okta.com/docs/concepts/auth-servers/&quot;&gt;Authorization servers&lt;/a&gt; to understand which one fits your case.&lt;/p&gt;

&lt;h3 id=&quot;grant-api-scopes-to-read-users-and-groups&quot;&gt;Grant API scopes to read users and groups&lt;/h3&gt;

&lt;p&gt;Our app asks Okta for a list of users and each user’s groups, but Okta won’t hand that over merely because we asked nicely. You must grant your application permission to make those calls.&lt;/p&gt;

&lt;p&gt;Back in your Okta org, we have a couple of changes to make:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Navigate to &lt;strong&gt;Applications and Resources&lt;/strong&gt; &amp;gt; &lt;strong&gt;Applications&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Select the application you created&lt;/li&gt;
  &lt;li&gt;On the &lt;strong&gt;General&lt;/strong&gt; tab, find the &lt;strong&gt;General Settings&lt;/strong&gt; section and press &lt;strong&gt;Edit&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Activate the &lt;strong&gt;Proof of possession&lt;/strong&gt; checkbox then press &lt;strong&gt;Save&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Open the &lt;strong&gt;Okta API Scopes&lt;/strong&gt; tab, find &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;okta.users.read&lt;/code&gt;, and press &lt;strong&gt;Grant&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That single scope covers both calls this app makes. Listing a user’s groups counts as reading a user resource, so you don’t need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;okta.groups.read&lt;/code&gt; as well. Handy!&lt;/p&gt;

&lt;h3 id=&quot;allow-cross-origin-requests-and-protect-the-access-token&quot;&gt;Allow cross-origin requests and protect the access token&lt;/h3&gt;

&lt;p&gt;Before the app can talk to Okta from a browser, we need to allow cross-origin requests:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Navigate to &lt;strong&gt;Security&lt;/strong&gt; &amp;gt; &lt;strong&gt;API&lt;/strong&gt; &amp;gt; &lt;strong&gt;Trusted Origins&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Confirm you see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:4200&lt;/code&gt; with both &lt;strong&gt;CORS&lt;/strong&gt; and &lt;strong&gt;Redirect&lt;/strong&gt; enabled&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We’re taking a shortcut in the demo by calling the Okta management API directly from the Angular app. Okta’s APIs are a backend that’s already in place, which means we can take the SDK for a spin without having to stand up resource APIs ourselves.&lt;/p&gt;

&lt;p&gt;The access token to call Okta’s APIs can perform sensitive admin actions, though, so let’s not send it around as a plain bearer token. Demonstrating Proof of Possession (DPoP) binds the token to a key pair that this browser generates and keeps to itself, so a stolen copy does a thief no good. The starter’s interceptor already sends the DPoP headers on every Okta request, so all that’s left is switching it on.&lt;/p&gt;

&lt;p&gt;If you want to learn more about DPoP in an Angular app, you’ll want to check out:&lt;/p&gt;

&lt;article class=&quot;link-container&quot; style=&quot;border: 1px solid silver; border-radius: 3px; padding: 12px 15px&quot;&gt;
              &lt;a href=&quot;/blog/2024/09/10/angular-dpop-jwt&quot; style=&quot;font-size: 1.375em; margin-bottom: 20px;&quot;&gt;
                &lt;span&gt;Secure OAuth 2.0 Access Tokens with Proofs of Possession&lt;/span&gt;
              &lt;/a&gt;
              &lt;p&gt;Elevate access token security by demonstrating proof of possession (DPoP). Migrate a SPA to use DPoP in this hands-on project.&lt;/p&gt;
              &lt;div&gt;&lt;div class=&quot;BlogPost-attribution&quot;&gt;
            &lt;a href=&quot;/blog/authors/alisa-duncan/&quot;&gt;
              &lt;img src=&quot;/assets-jekyll/avatar-alisa_duncan-b29fa4df50f5c99f536307c6bc0e5cb3434a922bdada7fe4f4b3cf8488299465.jpg&quot; alt=&quot;avatar-avatar-alisa_duncan.jpeg&quot; class=&quot;BlogPost-avatar&quot; /&gt;
            &lt;/a&gt;
            &lt;span class=&quot;BlogPost-author&quot;&gt;
                &lt;a href=&quot;/blog/authors/alisa-duncan/&quot;&gt;Alisa Duncan&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;&lt;/div&gt;
          &lt;/article&gt;

&lt;h2 id=&quot;add-authentication-with-the-standalone-okta-angular-provider&quot;&gt;Add authentication with the standalone Okta Angular provider&lt;/h2&gt;

&lt;p&gt;Here’s the part I’ve been looking forward to. Open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/app.config.ts&lt;/code&gt;. You already have an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OktaAuth&lt;/code&gt; instance. Replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{yourOktaDomain}&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{yourClientId}&lt;/code&gt; with the issuer URL and the client ID from the previous section. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;issuer&lt;/code&gt; value should look something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://integrator-123.okta.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are a couple of OIDC properties worth calling out:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scopes&lt;/code&gt;&lt;/strong&gt; includes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;okta.users.read&lt;/code&gt; alongside the usual OIDC scopes, which is how the access token ends up carrying the permission you granted a moment ago&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dpop: true&lt;/code&gt;&lt;/strong&gt; is the client half of the DPoP setting you switched on in the Okta app configuration. The SDK handles the key pair and the proof headers from here&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the OIDC configuration set, now it’s time to provide Okta authentication to the app. Update the app as shown:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;appConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ApplicationConfig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;providers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// other providers such as provideRouter and provideHttpClient here&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;provideOktaAuth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;withOktaConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oktaAuth&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s talk through the code.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provideOktaAuth()&lt;/code&gt;&lt;/strong&gt; is the provider function that registers the SDK. It sits in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;providers&lt;/code&gt; array next to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provideRouter&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provideHttpClient&lt;/code&gt;, exactly like every other Angular provider function&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;withOktaConfig({ oktaAuth })&lt;/code&gt;&lt;/strong&gt; passes your configuration in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve added Okta to an Angular app before, compare that to what you used to write:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;importProvidersFrom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;OktaAuthModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forRoot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;oktaAuth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;OktaAuth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* config */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OktaAuthModule&lt;/code&gt; is gone, and so is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;importProvidersFrom&lt;/code&gt; wrapper that existed only to drag an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NgModule&lt;/code&gt; into a standalone app. Nothing left to bridge. That’s my favorite kind of API change – the kind where code disappears. 🎉&lt;/p&gt;

&lt;h2 id=&quot;protect-routes-with-functional-route-guards&quot;&gt;Protect routes with functional route guards&lt;/h2&gt;

&lt;p&gt;The dashboard route is wide open right now. Before we close it, let’s see the problem. Start the app:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Navigate straight to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:4200/dashboard&lt;/code&gt; without signing in. The page renders, the API calls fail, and you get an error message. Not a great experience for anyone.&lt;/p&gt;

&lt;p&gt;Open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/app.routes.ts&lt;/code&gt; and add the guard to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dashboard&lt;/code&gt; route:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;OktaCallbackComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;canActivateAuthGuard&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@okta/okta-angular&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Routes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// other routes here. We're only updating the dashboard route&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;dashboard&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;loadComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./dashboard/dashboard&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Dashboard&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;canActivate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;canActivateAuthGuard&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// add the guard here&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;canActivateAuthGuard&lt;/code&gt; is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CanActivateFn&lt;/code&gt;, so it’s a function you drop into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;canActivate&lt;/code&gt; array. There’s no guard class to instantiate and no provider to register. It has two siblings worth knowing about: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;canActivateChildAuthGuard&lt;/code&gt; for child routes, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;canMatchAuthGuard&lt;/code&gt;, which pairs nicely with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loadChildren&lt;/code&gt; because it can reject a route before Angular downloads the lazy-loaded bundle.&lt;/p&gt;

&lt;p&gt;Try navigating to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dashboard&lt;/code&gt; while signed out now. Okta redirects you to sign in first. Much better!&lt;/p&gt;

&lt;h2 id=&quot;add-users-to-your-okta-org&quot;&gt;Add users to your Okta org&lt;/h2&gt;

&lt;p&gt;When we sign in, we see a user table. And you’re in it!&lt;/p&gt;

&lt;p&gt;However, a user portal with only one user isn’t much of a portal, so let’s add a few people to look at. Back in Okta:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Navigate to &lt;strong&gt;Directory&lt;/strong&gt; &amp;gt; &lt;strong&gt;People&lt;/strong&gt; and press &lt;strong&gt;Add person&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Fill in with your favorite first name, last name, and username (email), then press &lt;strong&gt;Save&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Repeat a couple of times&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add a totally made-up set of names for more fun. I went with Rosalind Park, Tobias Ferreira, and Ines Alvarez, but this is your org, and I’m not the boss of you.&lt;/p&gt;

&lt;p&gt;Ensure you assign the users to your Okta app. Depending on your Okta app’s access policy, users may be automatically assigned to apps, but it’s &lt;a href=&quot;https://help.okta.com/oie/en-us/content/topics/users-groups-profiles/usgp-assign-apps.htm&quot;&gt;always good to double check&lt;/a&gt;. You went through the effort to create users, so you want to see them in your dashboard, right?&lt;/p&gt;

&lt;p&gt;Every user you create automatically joins the built-in &lt;strong&gt;Everyone&lt;/strong&gt; group, which means the groups view you’re about to build has something to show from the first run. Thanks, Okta!&lt;/p&gt;

&lt;h2 id=&quot;display-users-and-review-angulars-rxresource-api&quot;&gt;Display users and review Angular’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rxResource&lt;/code&gt; API&lt;/h2&gt;

&lt;p&gt;Time to see what we have. Back in the running app, take a closer look at that table.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/angular-auth-signals/app-dashboard-61ad6637c8d7cde66505c35bc4c846eab86b3c3120fd2d721b52d25451444017.jpg&quot; alt=&quot;The user portal dashboard showing a table of users with name, email, and status columns, and a toggle button on each row&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Each row has a toggle button that currently does nothing. We’ll get there.&lt;/p&gt;

&lt;p&gt;Before we do, open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/dashboard/dashboard.ts&lt;/code&gt; and look at how that table gets its data:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;usersResource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rxResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;okta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;defaultValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rxResource&lt;/code&gt;, the signal-based way to wrap an observable that loads data. You give it a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stream&lt;/code&gt;, and it hands back a resource exposing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value()&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isLoading()&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error()&lt;/code&gt; as signals, which the template reads directly with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@if&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@for&lt;/code&gt;. No &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AsyncPipe&lt;/code&gt;, no subscription to unwind.&lt;/p&gt;

&lt;p&gt;This resource loads once and never changes, because nothing it depends on ever changes. The groups resource you’re about to write is interesting because it has to reload every time you pick a different user.&lt;/p&gt;

&lt;h2 id=&quot;load-a-users-groups-with-rxresource-and-a-signal-input&quot;&gt;Load a user’s groups with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rxResource&lt;/code&gt; and a signal input&lt;/h2&gt;

&lt;p&gt;Generate a component for the groups view:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ng g c UserGroups &lt;span class=&quot;nt&quot;&gt;--style&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;none &lt;span class=&quot;nt&quot;&gt;--skip-tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/user-groups/user-groups.ts&lt;/code&gt; and replace the contents with this:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@angular/core&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rxResource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@angular/core/rxjs-interop&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Okta&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;OktaService&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;../okta&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;OktaUser&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;../okta.types&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;app-user-groups&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;imports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;templateUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./user-groups.html&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;UserGroups&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;readonly&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oktaService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OktaService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OktaUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;groupsResource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rxResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;userId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oktaService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;defaultValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s step through the key parts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user = input.required&amp;lt;OktaUser&amp;gt;()&lt;/code&gt;&lt;/strong&gt; takes the user from the parent component. Inputs declared with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input()&lt;/code&gt; function are signals, which matters a lot for the next line.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;params&lt;/code&gt;&lt;/strong&gt; is where the reactivity lives. Reading &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.user().id&lt;/code&gt; inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;params&lt;/code&gt; subscribes the resource to that signal, so when the parent passes a different user, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;params&lt;/code&gt; recomputes, and the resource fetches that user’s groups on its own. You don’t wire up an effect, and you don’t call a reload method.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stream&lt;/code&gt;&lt;/strong&gt; receives the resolved &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;params&lt;/code&gt; and returns the observable to subscribe to. Destructuring it as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ params: userId }&lt;/code&gt; keeps the call readable.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defaultValue: []&lt;/code&gt;&lt;/strong&gt; means &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value()&lt;/code&gt; is an empty array instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;undefined&lt;/code&gt; before the first response, so the template doesn’t need a null check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create two computed properties, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupCount&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;displayGroups&lt;/code&gt; properties, in the component below the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupsResource&lt;/code&gt; definition:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;groupCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;groupsResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;displayGroups&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;groupsResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;computed&lt;/code&gt; properties derive from the resource, and they intentionally disagree with each other. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupCount&lt;/code&gt; reports every group the user belongs to, while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;displayGroups&lt;/code&gt; caps the list at three. Users in a real org can belong to a long list of groups, and I’d rather show an honest count than let a wall of panels shove the table off your screen.&lt;/p&gt;

&lt;p&gt;Now the template. Open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/user-groups/user-groups.html&lt;/code&gt; and add:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h2&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mb-4 text-sm font-semibold text-gray-700&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  {{ user().profile.firstName }} {{ user().profile.lastName }} groups
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

@if (groupsResource.isLoading()) {
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text-sm text-gray-500&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Loading groups...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
} @else if (groupsResource.error()) {
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text-sm text-red-600&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;role=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alert&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Failed to load groups.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
} @else {
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;py-3&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Group count: {{ groupCount() }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;grid gap-3 sm:grid-cols-2&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    @for (group of displayGroups(); track group.id) {
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;show group info here&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    } @empty {
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text-sm text-gray-500&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;No groups found.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    }
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice the template reads &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isLoading()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error()&lt;/code&gt; straight off the resource, so the loading and failure states come along for free. We’ll replace that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;show group info here&lt;/code&gt; placeholder in the next section.&lt;/p&gt;

&lt;p&gt;The next step is handing the component a user. Open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/dashboard/dashboard.html&lt;/code&gt; and find the row that appears when you toggle a user. Replace its placeholder content with the new component:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;app-user-groups&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;[user]=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserGroups&lt;/code&gt; to the dashboard’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imports&lt;/code&gt; array in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/dashboard/dashboard.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;UserGroups&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;../user-groups/user-groups&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;app-dashboard&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;imports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;UserGroups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;templateUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./dashboard.html&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Feel free to start the app and press the toggle on a user. You’ll see a group count of 1 and a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;show group info here&lt;/code&gt; for the &lt;strong&gt;Everyone&lt;/strong&gt; group. Toggle a different user and watch the panel refill itself – that’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;params&lt;/code&gt; doing its job. Pretty sweet!&lt;/p&gt;

&lt;h3 id=&quot;display-group-details-using-signal-inputs&quot;&gt;Display group details using signal inputs&lt;/h3&gt;

&lt;p&gt;The placeholder has served us well, and now it can go. Generate a component for a single group:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ng g c user-groups/group-panel &lt;span class=&quot;nt&quot;&gt;--inline-template&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--style&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;none &lt;span class=&quot;nt&quot;&gt;--skip-tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/user-groups/group-panel/group-panel.ts&lt;/code&gt; and replace the contents:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@angular/core&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;app-group-panel&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`
    &amp;lt;div class=&quot;rounded-xl border border-gray-200 bg-white p-4 shadow-sm&quot;&amp;gt;
      &amp;lt;h3 class=&quot;mb-1 text-base font-semibold text-gray-800&quot;&amp;gt;{{ heading() }}&amp;lt;/h3&amp;gt;
      &amp;lt;p class=&quot;text-sm text-gray-600&quot;&amp;gt;{{ description() }}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  `&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GroupPanel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;heading&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This component has no services, no resources, and no idea Okta exists. It takes two inputs and renders them; the separation of concerns is exactly what we want. It makes this component reusable and minimal. Note &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;description&lt;/code&gt; has a default of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;''&lt;/code&gt;, because group descriptions in Okta are optional.&lt;/p&gt;

&lt;p&gt;Back in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/app/user-groups/user-groups.html&lt;/code&gt;, swap the placeholder &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;p&amp;gt;show group info here&amp;lt;/p&amp;gt;&lt;/code&gt; for the panel:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;app-group-panel&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;[heading]=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;group.profile.name&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;[description]=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;group.profile.description ?? ''&quot;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then import it into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserGroups&lt;/code&gt; component:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GroupPanel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./group-panel/group-panel&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;Component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;app-user-groups&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;imports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;GroupPanel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;templateUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./user-groups.html&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Start the app and toggle a user open.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/angular-auth-signals/app-user-groups-be33697fcbb7e30a57f7787c37a9223a44df56760959399ca76d241ee991188a.jpg&quot; alt=&quot;The dashboard with a user row expanded, showing a group count and a panel for the Everyone group with its name and description&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This app is basic, but the data is real, and it reloads when it should. 🏆&lt;/p&gt;

&lt;p&gt;One group per user works, but it could be more exciting. We can create more groups and add our users to them.&lt;/p&gt;

&lt;p&gt;Back in your Okta org, navigate to &lt;strong&gt;Directory&lt;/strong&gt; &amp;gt; &lt;strong&gt;Groups&lt;/strong&gt;, press &lt;strong&gt;Add group&lt;/strong&gt;, and create a group with a name and description. Then add a couple of your users to it. Read &lt;a href=&quot;https://help.okta.com/en-us/content/topics/users-groups-profiles/usgp-groups-main.htm&quot;&gt;Manage groups&lt;/a&gt; if you want the full details. Toggle that user open again, and you’ll see the new panel show up alongside &lt;strong&gt;Everyone&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;load-your-okta-configuration-at-runtime&quot;&gt;Load your Okta configuration at runtime&lt;/h2&gt;

&lt;p&gt;Right now, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.config.ts&lt;/code&gt; hardcodes the Okta configuration, so changing your issuer or client ID means a rebuild and a redeploy. If you’d rather fetch those values when the app starts, the new SDK still supports it. I wrote about this pattern in &lt;a href=&quot;/blog/2024/02/28/okta-authentication-angular&quot;&gt;Flexible Authentication Configurations in Angular Applications Using Okta&lt;/a&gt;, and the good news is it survived the move to standalone providers.&lt;/p&gt;

&lt;p&gt;The change has two halves. Set the configuration during app initialization and call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provideOktaAuth()&lt;/code&gt; with no configuration. Something like this:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;configInitializer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;configService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OktaAuthConfigService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/api/config.json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Couldn't load the Okta configuration: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;authConfig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;configService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;oktaAuth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;OktaAuth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;authConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;redirectUri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/login/callback`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;scopes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;openid&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;profile&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;offline_access&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;okta.users.read&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;pkce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;dpop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;appConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ApplicationConfig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;providers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// remaining providers&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;provideAppInitializer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;configInitializer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;provideOktaAuth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s talk through what changed.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provideOktaAuth()&lt;/code&gt;&lt;/strong&gt; now takes no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;withOktaConfig()&lt;/code&gt; feature. The SDK treats its configuration as optional at provide time, and builds the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OktaAuth&lt;/code&gt; instance the first time something injects it, which is after initialization finishes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provideAppInitializer()&lt;/code&gt;&lt;/strong&gt; replaces the old &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APP_INITIALIZER&lt;/code&gt; multi-provider. It takes a plain function, so you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inject()&lt;/code&gt; your dependencies inside it rather than declaring a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deps&lt;/code&gt; array. Return a promise, and Angular waits for it before starting the app.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt;&lt;/strong&gt; reads the configuration. We’re not using Angular’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpClient&lt;/code&gt; here, and we’re not coding workarounds to get around the interceptor either.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;response.ok&lt;/code&gt;&lt;/strong&gt; guards the parse. When that path 404s, most servers hand back an HTML error page, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;response.json()&lt;/code&gt; fails with a parse error that tells you nothing about what actually went wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One sequencing detail that will bite you later if you don’t know it now: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inject()&lt;/code&gt; has to run &lt;em&gt;before&lt;/em&gt; your first &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;await&lt;/code&gt;. Once you await, you’ve left the injection context and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inject()&lt;/code&gt; throws. Evaluating it in the parameter list works, as does calling it on the first line of the body. 👀&lt;/p&gt;

&lt;p&gt;Runtime configuration lets you point the same app to a different Okta org without rebuilding it. Still the ultimate flexibility!&lt;/p&gt;

&lt;p&gt;Before you move on, let’s talk about what belongs in that config file. Fetching it without an access token is fine here. The request stays on your own origin, and the values it returns are already public. The JavaScript bundle includes your issuer and client ID. Anyone can read them out of your app today.&lt;/p&gt;

&lt;table&gt;
&lt;tr&gt;
    &lt;td style=&quot;font-size: 3rem;&quot;&gt;⚠️&lt;/td&gt;
    &lt;td&gt;
      &lt;strong&gt;Heads up!&lt;/strong&gt; &lt;br /&gt;
      Anyone can request &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/api/config.json&lt;/code&gt;, so nothing sensitive goes in it. This is a browser-based app, which means it cannot keep a secret. A single-page app can’t maintain secrets. If a value would hurt you when published, it belongs on a server you control.
    &lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;h2 id=&quot;learn-more-about-angular-signals-standalone-apis-and-oidc&quot;&gt;Learn more about Angular signals, standalone APIs, and OIDC&lt;/h2&gt;

&lt;p&gt;In this post, you added authentication to a standalone Angular app with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provideOktaAuth&lt;/code&gt;, protected a route with a functional guard, and loaded each user’s groups with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rxResource&lt;/code&gt; driven by a signal input. I hope you enjoyed it! You can find the completed project in the &lt;a href=&quot;https://github.com/oktadev/okta-angular-auth-signals-example&quot;&gt;okta-angular-auth-signals-example&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;p&gt;If you liked this post, check out these resources.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2024/02/28/okta-authentication-angular&quot;&gt;Flexible Authentication Configurations in Angular Applications Using Okta&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2024/09/10/angular-dpop-jwt&quot;&gt;Secure OAuth 2.0 Access Tokens with Proofs of Possession&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2024/03/12/stepup-authentication&quot;&gt;Add Step-up Authentication Using Angular and NestJS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://angular.dev/guide/signals/resource&quot;&gt;Angular’s resource and rxResource guide&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/okta/okta-angular&quot;&gt;Okta Angular SDK on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember to follow us on &lt;a href=&quot;https://www.linkedin.com/company/oktadev&quot;&gt;LinkedIn&lt;/a&gt; and subscribe to our &lt;a href=&quot;https://www.youtube.com/c/oktadev&quot;&gt;YouTube&lt;/a&gt; for more exciting content. We also want to hear from you about topics you’d like to see and any questions you may have. Leave us a comment below!&lt;/p&gt;
</description>
        <pubDate>Tue, 25 Aug 2026 00:00:00 -0500</pubDate>
        <link>https://developer.okta.com/blog/2026/08/25/angular-auth-signals</link>
        <guid isPermaLink="true">https://developer.okta.com/blog/2026/08/25/angular-auth-signals</guid>
      </item>
    
      <item>
        <title>Add Cross App Access to Your OIDC Resource Application</title>
        <description>&lt;p&gt;If you currently federate enterprise customers using OpenID Connect (OIDC) and want to allow applications to access your API on behalf of those users, this Cross App Access (XAA) guide is for you.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/&quot;&gt;Identity Assertion Authorization Grant specification&lt;/a&gt;, the basis of XAA, was designed with OIDC in mind. Your authorization server already trusts the customer’s IdP for single sign-on (SSO), and XAA reuses that same trust for API access. This guide details what you need to support, how to validate the grant, and how to resolve the user at your resource authorization server.&lt;/p&gt;

&lt;p&gt;&lt;strong class=&quot;hide&quot;&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#how-xaa-in-oidc-works&quot; id=&quot;markdown-toc-how-xaa-in-oidc-works&quot;&gt;How XAA in OIDC works&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#analyzing-the-id-jag-claims&quot; id=&quot;markdown-toc-analyzing-the-id-jag-claims&quot;&gt;Analyzing the ID-JAG claims&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#xaa-implementation-checklist-for-oidc-federated-applications&quot; id=&quot;markdown-toc-xaa-implementation-checklist-for-oidc-federated-applications&quot;&gt;XAA implementation checklist for OIDC-federated applications&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#mapping-user-identity-from-iss-and-sub&quot; id=&quot;markdown-toc-mapping-user-identity-from-iss-and-sub&quot;&gt;Mapping user identity from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#validating-the-id-jag-and-resolving-the-user&quot; id=&quot;markdown-toc-validating-the-id-jag-and-resolving-the-user&quot;&gt;Validating the ID-JAG and resolving the user&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#issuing-the-access-token&quot; id=&quot;markdown-toc-issuing-the-access-token&quot;&gt;Issuing the access token&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#updating-authorization-server-metadata&quot; id=&quot;markdown-toc-updating-authorization-server-metadata&quot;&gt;Updating authorization server metadata&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#making-cross-application-requests-from-your-oidc-app-securely&quot; id=&quot;markdown-toc-making-cross-application-requests-from-your-oidc-app-securely&quot;&gt;Making cross-application requests from your OIDC app securely&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#configure-your-xaa-oidc-resource-app-in-okta&quot; id=&quot;markdown-toc-configure-your-xaa-oidc-resource-app-in-okta&quot;&gt;Configure your XAA OIDC Resource app in Okta&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#create-the-oidc-requesting-app-for-testing&quot; id=&quot;markdown-toc-create-the-oidc-requesting-app-for-testing&quot;&gt;Create the OIDC requesting app for testing&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#create-the-oidc-resource-app-in-okta&quot; id=&quot;markdown-toc-create-the-oidc-resource-app-in-okta&quot;&gt;Create the OIDC resource app in Okta&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#register-and-configure-the-ai-agent-in-okta&quot; id=&quot;markdown-toc-register-and-configure-the-ai-agent-in-okta&quot;&gt;Register and configure the AI Agent in Okta&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#verify-your-okta-xaa-setup-on-xaadev&quot; id=&quot;markdown-toc-verify-your-okta-xaa-setup-on-xaadev&quot;&gt;Verify your Okta XAA setup on xaa.dev&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#configure-oidc-sso&quot; id=&quot;markdown-toc-configure-oidc-sso&quot;&gt;Configure OIDC SSO&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#verify-the-refresh-token-exchange-for-an-id-jag-token&quot; id=&quot;markdown-toc-verify-the-refresh-token-exchange-for-an-id-jag-token&quot;&gt;Verify the refresh token exchange for an ID-JAG token&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#redeem-the-id-jag-for-an-access-token-at-the-resource-authorization-server&quot; id=&quot;markdown-toc-redeem-the-id-jag-for-an-access-token-at-the-resource-authorization-server&quot;&gt;Redeem the ID-JAG for an access token at the resource authorization server&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#call-the-resource-api-with-the-access-token&quot; id=&quot;markdown-toc-call-the-resource-api-with-the-access-token&quot;&gt;Call the resource API with the access token&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#prove-the-xaa-connection-end-to-end&quot; id=&quot;markdown-toc-prove-the-xaa-connection-end-to-end&quot;&gt;Prove the XAA connection end-to-end&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#takeaways-for-implementors-who-also-have-saml-apps&quot; id=&quot;markdown-toc-takeaways-for-implementors-who-also-have-saml-apps&quot;&gt;Takeaways for implementors who also have SAML apps&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#learn-more-about-cross-app-access-oidc-and-oauth-20&quot; id=&quot;markdown-toc-learn-more-about-cross-app-access-oidc-and-oauth-20&quot;&gt;Learn more about Cross App Access, OIDC, and OAuth 2.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;how-xaa-in-oidc-works&quot;&gt;How XAA in OIDC works&lt;/h2&gt;

&lt;p&gt;When an agent (like one running in Claude) needs API access, it presents an &lt;strong&gt;Identity Assertion Authorization Grant (ID-JAG)&lt;/strong&gt;. The ID-JAG is a short-lived JSON Web Token (JWT) issued by the customer’s Identity Provider (IdP) for your authorization server. Your resource server accepts the token, identifies the user, and issues your own access token, all while leaving the customer’s existing OIDC integration untouched.&lt;/p&gt;

&lt;p&gt;The sequence diagram shown below describes the OIDC XAA flow. Notice that the OIDC SSO flow stays the same; the only change is the section highlighted with the comment “Your Resource Authorization Server (AS): redeem and resolve”. You’ll make a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt; request to your resource’s authorization server with the ID-JAG, resolve the subject, and return an access token that you’ll use for resource requests.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/xaa-oidc-resource/xaa-oidc-sequence-diagram-e66c7e69cde77866c0cabc9ffb0dec6b03f34a0fd1d5f0b2ff49551799f04ea5.svg&quot; alt=&quot;Sequence diagram showing OIDC SSO between the user and Okta IdP, a token exchange producing an ID-JAG, and the resource authorization server redeeming the ID-JAG and resolving the subject before issuing an access token used to call the API.&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;You are not participating in the client’s SSO here. The only artifact crossing from the IdP to your domain is the ID-JAG. Everything upstream, including authentication, ID token issuance, and the token exchange, happens without you. Your responsibility is to validate the ID-JAG, redeem it for an access token, and resolve the user from the claims.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;analyzing-the-id-jag-claims&quot;&gt;Analyzing the ID-JAG claims&lt;/h2&gt;

&lt;p&gt;When you decode the ID-JAG, you’ll see claims in the header and payload that impact how you process the access request:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;typ&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;oauth-id-jag+jwt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;iss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://atko.okta.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sub&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;00u1a2b3c4D5e6F7g8h9&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;aud&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://auth.chat.example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;client_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0oa8claudeMcpAtYourAS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;alice@atko.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;scope&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;chat:read chat:write&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;jti&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id-jag-7f3c9a21b8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Focus on these key claims noted in the decoded ID-JAG payload:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt;&lt;/strong&gt;: Together, these are the primary key for user resolution. Neither one is unique on its own&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt;&lt;/strong&gt;: Indicates the issuer identifier for your resource authorization server&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_id&lt;/code&gt;&lt;/strong&gt;: This is the client’s ID at your resource authorization server, which might differ from its ID at the IdP&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;email&lt;/code&gt;&lt;/strong&gt;: Recommended by the specification for just-in-time provisioning if the user has not yet signed in&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jti&lt;/code&gt;&lt;/strong&gt;: The unique ID for this ID-JAG JWT, as required by JWT conventions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three optional claims matter if you run a multi-tenant service: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tenant&lt;/code&gt; claim scopes the subject when the IdP itself is multi-tenant, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud_tenant&lt;/code&gt; scopes the subject when &lt;em&gt;your&lt;/em&gt; resource authorization server is multi-tenant, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud_sub&lt;/code&gt; carries the identifier that the IdP believes &lt;em&gt;you&lt;/em&gt; already have for this user.&lt;/p&gt;

&lt;h2 id=&quot;xaa-implementation-checklist-for-oidc-federated-applications&quot;&gt;XAA implementation checklist for OIDC-federated applications&lt;/h2&gt;

&lt;p&gt;To fully support Cross App Access, implement these four steps in sequence:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#mapping-user-identity-from-iss-and-sub&quot;&gt;Mapping user identity from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#validating-the-id-jag-and-resolving-the-user&quot;&gt;Validating the ID-JAG and resolving the user&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#issuing-the-access-token&quot;&gt;Issuing the access token&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#updating-authorization-server-metadata&quot;&gt;Updating authorization server metadata&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;mapping-user-identity-from-iss-and-sub&quot;&gt;Mapping user identity from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;You already resolve users from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt; claim during SSO, so the ID-JAG uses the identifier you know. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt; claim alone is not the key, though.&lt;/p&gt;

&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt; value is only unique when scoped with the issuer. Resolve on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt; together. If the IdP is multi-tenant, the scope is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tenant&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt;, and you’ll need the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tenant&lt;/code&gt; claim to disambiguate. Two customers on the same multi-tenant IdP can otherwise collide.&lt;/p&gt;

&lt;p&gt;If the user hasn’t signed in to your app yet, there’s no local record to resolve. Use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;email&lt;/code&gt; claim, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud_sub&lt;/code&gt; when the IdP supplies it, to provision just in time. Treat &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;email&lt;/code&gt; as a linking hint rather than a primary key. Email addresses get reassigned when employees leave, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt; does not.&lt;/p&gt;

&lt;h3 id=&quot;validating-the-id-jag-and-resolving-the-user&quot;&gt;Validating the ID-JAG and resolving the user&lt;/h3&gt;

&lt;p&gt;The client posts the ID-JAG as a JWT authorization grant and authenticates with its credentials at your server. Below is an example HTTP request for requesting an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;POST&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;/oauth2/v1/token&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;chat.example&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Basic &amp;lt;base64(client_id:client_secret)&amp;gt;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/x-www-form-urlencoded&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;grant_type=urn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ietf:params:oauth:grant-type:jwt-bearer&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;&amp;amp;assertion=eyJ0eXAiOiJvYXV0aC1pZC1qYWcrand0...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Before processing, you must bind the ID-JAG’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt; to a registered IdP connection to prevent forgery.&lt;/p&gt;

&lt;p&gt;If you verify the signature before checking the issuer binding, an attacker could stand up their own IdP, sign a token, and present a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt; belonging to one of your users.&lt;/p&gt;

&lt;p&gt;Always resolve the connection from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt; first, then verify the signature against that connection’s key. You’ll compare this using the JSON Web Key Set (JWKS) metadata.&lt;/p&gt;

&lt;p&gt;Below is the pseudocode for implementing the validation and resolving a user:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;connections&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;https://atko.okta.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;jwks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;&quot;https://atko.okta.com/oauth2/v1/keys&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;multiTenant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;allowJit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;redeem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idJag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticatedClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Bind&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iss&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trusting&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;signature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;iss&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unverified_issuer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idJag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connections&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reject&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;invalid_grant&quot;&lt;/span&gt;

    &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;2.&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Verify&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;signature&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;against&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;the&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;specific&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;issuers&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;JWKS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;verify_jwt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idJag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jwks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jwks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;invalid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reject&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;invalid_grant&quot;&lt;/span&gt;

    &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;5.&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Perform&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;remaining&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;checks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;typ&lt;/span&gt;       &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;oauth-id-jag+jwt&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;aud&lt;/span&gt;       &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;resource_authorization_server_url&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticatedClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolveSubject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;applyScopePolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;issueAccessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;resolveSubject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;multiTenant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tenant&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lookup_user_by_federated_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tenant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;none&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allowJit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;provision_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tenant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reject&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;invalid_grant&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;issuing-the-access-token&quot;&gt;Issuing the access token&lt;/h3&gt;

&lt;p&gt;Once you resolve the user, issue an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt; scoped according to your local policy. Below is an example of an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt; returned after successfully validating the ID-JAG and resolving the user.&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;OK&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/json;charset=UTF-8&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Cache-Control&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;no-store&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;token_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Bearer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;access_token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2YotnFZFEjr1zCsicMWpAA&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;expires_in&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;86400&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;scope&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;chat:read chat:write&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;Do not issue a refresh token. If your authorization server issues a refresh token, the client has durable access to your resource server, and the IdP cannot revoke access.&lt;/p&gt;

  &lt;p&gt;The ID-JAG replaces the need for a refresh token. On access token expiry, the client resubmits the same ID-JAG to your token endpoint, and you mint a new access token against it. Only once the ID-JAG itself expires does the client return to the IdP for a new one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;updating-authorization-server-metadata&quot;&gt;Updating authorization server metadata&lt;/h3&gt;

&lt;p&gt;Clients locate your XAA support via your authorization server metadata (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/.well-known/oauth-authorization-server&lt;/code&gt;). Ensure you include the supported fields:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;issuer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://chat.example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;token_endpoint&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://auth.chat.example/oauth2/v1/token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;grant_types_supported&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;urn:ietf:params:oauth:grant-type:jwt-bearer&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;authorization_grant_profiles_supported&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;urn:ietf:params:oauth:grant-profile:id-jag&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Publish the profile identifier and nothing more. Don’t list the issuers you trust in public metadata, since that discloses your customer relationships. If clients need to check whether you accept a given issuer, put that check behind client authentication.&lt;/p&gt;

&lt;h2 id=&quot;making-cross-application-requests-from-your-oidc-app-securely&quot;&gt;Making cross-application requests from your OIDC app securely&lt;/h2&gt;

&lt;p&gt;With these four steps complete, you’ve configured your OIDC application for Cross App Access. Agents can now authorize requests against your API using the federation you already run.&lt;/p&gt;

&lt;p&gt;You can now use Okta to make cross-application requests with your OIDC app.&lt;/p&gt;

&lt;p&gt;Before you configure anything, here’s who runs what in this walkthrough:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Component&lt;/th&gt;
      &lt;th&gt;Role&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Your resource server and API&lt;/td&gt;
      &lt;td&gt;Runs on your infrastructure. The service XAA protects.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Your resource authorization server (your resource AS)&lt;/td&gt;
      &lt;td&gt;Runs on your infrastructure. Mints and validates the access tokens your API accepts.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Your Okta org&lt;/td&gt;
      &lt;td&gt;The IdP. Authenticates users and issues the ID-JAG.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;The Okta app integration representing your resource app&lt;/td&gt;
      &lt;td&gt;A registration in Okta, not a running service. Holds the XAA configuration Okta uses to mint ID-JAGs for your resource AS.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;The Okta app integration representing the requesting app&lt;/td&gt;
      &lt;td&gt;A registration in Okta, not a running service. Lets users sign in to the AI agent and lets Okta issue ID-JAGs on its behalf.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;xaa.dev&lt;/td&gt;
      &lt;td&gt;Plays the requesting app for this walkthrough, running against your resource server.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;xaa.dev drives the requesting side so you can verify the resource side, which is the only side you’re actually building.&lt;/p&gt;

&lt;h2 id=&quot;configure-your-xaa-oidc-resource-app-in-okta&quot;&gt;Configure your XAA OIDC Resource app in Okta&lt;/h2&gt;

&lt;p&gt;This walkthrough tests an XAA implementation, it doesn’t build one. Before you continue, you’ll need already running:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Your resource AS, with a reachable issuer URL and token endpoint&lt;/li&gt;
  &lt;li&gt;The ability to register an OAuth client at your resource AS and issue it a client ID and secret, since the requesting app authenticates there at redemption&lt;/li&gt;
  &lt;li&gt;The &lt;a href=&quot;#xaa-implementation-checklist-for-oidc-federated-applications&quot;&gt;ID-JAG redemption logic from the checklist&lt;/a&gt; earlier in this post, implemented&lt;/li&gt;
  &lt;li&gt;An API endpoint that accepts the access token your resource AS issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you haven’t built these yet, work through the checklist sections above first.&lt;/p&gt;

&lt;p&gt;You’ll also need:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;An Okta Integrator Free Plan account. &lt;a href=&quot;https://developer.okta.com/signup/&quot;&gt;Sign up for a new account&lt;/a&gt; to test out the XAA feature&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://xaa.dev/developer/test-resource-app?tab=oidc&quot;&gt;xaa.dev&lt;/a&gt; to drive the requesting side while you test your resource server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the guide’s &lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#overview&quot;&gt;prerequisites for AI agent-to-app XAA&lt;/a&gt; for the underlying Okta org requirements.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Open the &lt;a href=&quot;https://xaa.dev/developer/test-resource-app?tab=oidc&quot;&gt;xaa.dev resource app tester&lt;/a&gt; and enter any validly formatted email address to establish a session&lt;/li&gt;
  &lt;li&gt;In &lt;strong&gt;Your IdP’s issuer URL&lt;/strong&gt;, enter your Okta org issuer URL. This is the IdP field, not the resource AS issuer URL field further down the panel. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://integrator-xxxxxx.okta.com&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Copy the generated &lt;strong&gt;Sign-in redirect URI&lt;/strong&gt;, for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://auth.resource.xaa.dev/api/federated-sso/callback/resapp-oidc-xxxxxxx&lt;/code&gt;. You’ll paste it into the requesting app integration in the next section&lt;/li&gt;
  &lt;li&gt;Leave the tab open. You’ll return to it after the Okta configuration is complete&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;create-the-oidc-requesting-app-for-testing&quot;&gt;Create the OIDC requesting app for testing&lt;/h3&gt;

&lt;p&gt;You’ll need a requesting app integration and a resource app integration set up in Okta. Create the requesting app integration by following the guide’s &lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#supported-resource-apps&quot;&gt;OIDC app creation steps&lt;/a&gt;, using these tutorial-specific values:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;App integration name&lt;/strong&gt;: “Requesting App”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Grant type&lt;/strong&gt;: Authorization Code, plus &lt;strong&gt;Refresh Token&lt;/strong&gt; — you’ll need it to request the ID-JAG&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Sign-in redirect URIs&lt;/strong&gt;: the &lt;strong&gt;Sign-in redirect URI&lt;/strong&gt; you copied from xaa.dev above&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Assignments&lt;/strong&gt;: keep &lt;strong&gt;Skip group assignments for now&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;General configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Select the &lt;strong&gt;General&lt;/strong&gt; tab and copy the &lt;strong&gt;Client ID&lt;/strong&gt; and &lt;strong&gt;Client secret&lt;/strong&gt;. Paste both into the requesting app fields at &lt;a href=&quot;https://xaa.dev/developer/test-resource-app?tab=oidc&quot;&gt;xaa.dev&lt;/a&gt; and save.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assignments configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Assign your test user to the requesting app integration. See &lt;a href=&quot;https://help.okta.com/okta_help.htm?type=oie&amp;amp;id=ext-lcm-assign-app-user&quot;&gt;Assign an app integration to a user&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Note: The values here come from your Resource Authorization Server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;create-the-oidc-resource-app-in-okta&quot;&gt;Create the OIDC resource app in Okta&lt;/h3&gt;

&lt;p&gt;Create the resource app integration the same way you created the requesting app integration, and use these tutorial-specific values:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;App integration name&lt;/strong&gt;: “Resource App”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Grant type&lt;/strong&gt;: Authorization Code&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Sign-in redirect URIs&lt;/strong&gt;: any valid placeholder URL. This app integration never performs an interactive login in this walkthrough; it only holds the Resource Server XAA configuration and the user assignment, and Okta requires the field to be non-empty. Don’t reuse the xaa.dev URI here&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Assignments&lt;/strong&gt;: keep &lt;strong&gt;Skip group assignments for now&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After creating the app integration, select the &lt;strong&gt;General&lt;/strong&gt; tab to get the &lt;strong&gt;Client ID&lt;/strong&gt; and &lt;strong&gt;Client secret&lt;/strong&gt;. Use these credentials in your resource server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource Server extra configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#enable-xaa-on-a-custom-app-integration&quot;&gt;Enable Cross App Access (XAA) on the resource app integration&lt;/a&gt; from the &lt;strong&gt;Resource Server&lt;/strong&gt; tab, and configure:
Note: The values here come from your test Requestor SSO App on xaa.dev.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Issuer URL&lt;/strong&gt;: Your resource AS issuer URL, the issuer of the authorization server you run. Entering it here tells Okta which external server to mint ID-JAGs for; Okta doesn’t host this server. This value becomes the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt; claim in the ID-JAG and cannot change without deleting and resetting the connection.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Audience/tenant ID&lt;/strong&gt;: This is optional and not needed for this walkthrough&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;This &lt;strong&gt;Issuer URL&lt;/strong&gt; (your resource AS issuer URL) must exactly match the &lt;strong&gt;Resource AS issuer (ID-JAG audience)&lt;/strong&gt; field you’ll enter on xaa.dev during verification. A mismatch still produces a correctly signed ID-JAG, but redemption fails the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt; check, and that’s the hardest failure in this tutorial to diagnose.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Assignments configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Assign your test user to the resource app integration. See &lt;a href=&quot;https://help.okta.com/okta_help.htm?type=oie&amp;amp;id=ext-lcm-assign-app-user&quot;&gt;Assign an app integration to a user&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;register-and-configure-the-ai-agent-in-okta&quot;&gt;Register and configure the AI Agent in Okta&lt;/h3&gt;

&lt;p&gt;With your requesting app integration and resource app integration configured, register a new AI Agent in Okta. During registration, you’ll link your requesting app integration under &lt;strong&gt;User access and authentication&lt;/strong&gt;, register the agent’s own OAuth client, and then connect the resource app integration as a &lt;strong&gt;Resource Connection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Register the AI Agent and link your requesting app integration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#register-ai-agent-user-access-and-authentication&quot;&gt;Register the AI Agent and link its requesting app integration&lt;/a&gt;. Use these tutorial-specific values:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Name&lt;/strong&gt;: “Agent”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Allow users to access this agent&lt;/strong&gt;: &lt;strong&gt;Select an existing app&lt;/strong&gt;, then choose “Requesting App”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Add owners&lt;/strong&gt;: assign your test user as an individual owner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This linked app integration is what your users sign in through to reach the agent: they authenticate against it, and the agent then acts on their behalf.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect the resource app integration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#configure-the-xaa-connection&quot;&gt;Add a resource connection to the AI agent&lt;/a&gt;. Use these tutorial-specific values:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Application instance&lt;/strong&gt;: “Resource App”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AI agent’s client ID registered in this app&lt;/strong&gt;: “this app” here means your resource AS, not the Okta app integration. Enter the client ID you issued to the AI agent when you registered it as an OAuth client at your resource AS. You perform that registration outside Okta, and the process varies by product&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scopes&lt;/strong&gt;: &lt;strong&gt;Allow any scope&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Activate the AI Agent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Activating the linked requesting app integration usually activates the AI Agent. If the agent’s status is &lt;strong&gt;STAGED&lt;/strong&gt;, &lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#activate-the-ai-agent&quot;&gt;activate it manually&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once the AI Agent is active, the configuration is complete. Except for Machine access, all checkmarks on the agent configuration page must be green.&lt;/p&gt;

&lt;h2 id=&quot;verify-your-okta-xaa-setup-on-xaadev&quot;&gt;Verify your Okta XAA setup on xaa.dev&lt;/h2&gt;

&lt;p&gt;Open the AI Agent you created, then go to &lt;strong&gt;Client registration&lt;/strong&gt; tab: You’ll see the agent uses the same credentials as the linked requesting app. We will use this client ID and secret to request an ID-JAG from the IdP.&lt;/p&gt;

&lt;p&gt;In xaa.dev, enter the following values:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Client ID&lt;/strong&gt;: Use the values from the AI Agent’s client registration tab.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Client Secret&lt;/strong&gt;: Use the values from the AI Agent’s client registration tab.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Resource AS issuer (ID-JAG audience)&lt;/strong&gt;: Use your resource AS issuer URL. This must exactly match the &lt;strong&gt;Issuer URL&lt;/strong&gt; you set on the resource app integration’s Resource Server tab.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scopes&lt;/strong&gt;: Enter the scopes you want to request for the access token. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat:read chat:write&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Press &lt;strong&gt;Save&lt;/strong&gt; to store the values.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By this point, you’ll have every value from the checklist and your one-time Okta setup in place (AI Agent, owner, delegation, and resource connection), so we’ll add the values from Okta and the apps to walk through the flow step by step, one button per step.&lt;/p&gt;

&lt;h3 id=&quot;configure-oidc-sso&quot;&gt;Configure OIDC SSO&lt;/h3&gt;

&lt;p&gt;Press &lt;strong&gt;Start OIDC login at your IdP&lt;/strong&gt; and complete the login in the pop-up.&lt;/p&gt;

&lt;p&gt;When it closes, the step turns green and shows a &lt;strong&gt;✓ Auto-discovered SSO&lt;/strong&gt; endpoint, confirming that the tester resolved the real authorization and token endpoints from your org’s discovery document and returned an ID token and a refresh token.&lt;/p&gt;

&lt;h3 id=&quot;verify-the-refresh-token-exchange-for-an-id-jag-token&quot;&gt;Verify the refresh token exchange for an ID-JAG token&lt;/h3&gt;

&lt;p&gt;Press &lt;strong&gt;Exchange refresh token for ID-JAG&lt;/strong&gt;. The tester posts the refresh token from sign-in to your IdP’s token endpoint and returns a decoded ID-JAG. Take a second to review it: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt; should equal your resource authorization issuer, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt; should be the identifier for the user who logged in. A 200 OK indicates that the step succeeded.&lt;/p&gt;

&lt;h3 id=&quot;redeem-the-id-jag-for-an-access-token-at-the-resource-authorization-server&quot;&gt;Redeem the ID-JAG for an access token at the resource authorization server&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Fill in your resource AS token endpoint&lt;/li&gt;
  &lt;li&gt;The client ID and secret you issued to the AI agent when you registered it as an OAuth client at your resource AS. This must be the same client ID you entered in the resource connection step, or redemption fails the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_id&lt;/code&gt; check&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Press &lt;strong&gt;Redeem&lt;/strong&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grant_type=jwt-bearer&lt;/code&gt;). If the request succeeds, you’ll receive a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200 OK&lt;/code&gt; response with an access token. Inspect the token in the &lt;strong&gt;Token&lt;/strong&gt; tab to verify that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope&lt;/code&gt; claims match the values configured in your resource authorization server. This validation confirms that the authorization server accepted the ID-JAG and issued its own access token.&lt;/p&gt;

&lt;h3 id=&quot;call-the-resource-api-with-the-access-token&quot;&gt;Call the resource API with the access token&lt;/h3&gt;

&lt;p&gt;Select the request method and enter your API URL (xaa.dev adds the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization: Bearer&lt;/code&gt; header automatically, but you can add any other headers or a request body as needed), then press &lt;strong&gt;Send GET Request&lt;/strong&gt;. A 200 response from your endpoint is the final proof: your API accepts the access token generated by the ID-JAG exchange.&lt;/p&gt;

&lt;h3 id=&quot;prove-the-xaa-connection-end-to-end&quot;&gt;Prove the XAA connection end-to-end&lt;/h3&gt;

&lt;p&gt;A green &lt;strong&gt;Conformance passed&lt;/strong&gt; panel appears. Select &lt;strong&gt;Export conformance log (JSON)&lt;/strong&gt; to download the test results. The export includes the signed ID-JAG, the access token returned by your resource authorization server, and the API response.&lt;/p&gt;

&lt;p&gt;You can either download the log or have a URL to share with your IdP as proof that the Cross App Access integration works successfully.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/xaa-oidc-resource/export-conformance-log-2f2681448de69cdc10b6e563ed39125e566c8132e423890cb2774a820349c1f0.jpg&quot; alt=&quot;Conformance passed. Export your proof. A button allows exporting a conformance log in JSON format.&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;takeaways-for-implementors-who-also-have-saml-apps&quot;&gt;Takeaways for implementors who also have SAML apps&lt;/h2&gt;

&lt;p&gt;If some of your enterprise customers federate over SAML instead, here’s what changes when you extend XAA to those connections:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The subject arrives in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub_id&lt;/code&gt; claim using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;saml-nameid&lt;/code&gt; format, rather than in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Match on every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;saml-nameid&lt;/code&gt; member (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;issuer&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NameID&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sp_name_qualifier&lt;/code&gt;), rather than on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iss&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Everything else, including token issuance rules and redemption checks, remains as is&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;learn-more-about-cross-app-access-oidc-and-oauth-20&quot;&gt;Learn more about Cross App Access, OIDC, and OAuth 2.0&lt;/h2&gt;

&lt;p&gt;If this guide helped you implement Cross App Access with OIDC, explore these resources:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;📘 &lt;a href=&quot;https://help.okta.com/oie/en-us/content/topics/apps/apps-cross-app-access.htm&quot;&gt;Cross App Access Documentation&lt;/a&gt;: Official guides for configuring and managing Cross App Access in production.&lt;/li&gt;
  &lt;li&gt;🎙️ &lt;a href=&quot;https://www.youtube.com/watch?v=qKs4k5Y1x_s&quot;&gt;Developer Podcast on MCP and Cross App Access&lt;/a&gt;: Hear the backstory, use cases, and why this matters for developers.&lt;/li&gt;
  &lt;li&gt;📋 &lt;a href=&quot;/blog/2026/07/06/submit-oin-xaa&quot;&gt;How to Build and List Secure Cross App Access (XAA) Connections on Okta Integration Network (OIN)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Identity 101:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.okta.com/identity-101/whats-the-difference-between-oauth-openid-connect-and-saml/&quot;&gt;What’s the Difference Between OAuth, OpenID Connect, and SAML?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.okta.com/en-in/identity-101/saml-vs-oauth/&quot;&gt;What are SAML, OAuth, and OIDC?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.okta.com/identity-101/why-you-should-migrate-to-oauth-2-0-from-static-api-tokens/&quot;&gt;Why You Should Migrate to OAuth 2.0 From Static API Tokens&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/07/27/enterprise-ready-getting-started&quot;&gt;How to Get Going with the On-Demand SaaS Apps Workshops&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow us on &lt;a href=&quot;https://www.linkedin.com/company/oktadev&quot;&gt;LinkedIn&lt;/a&gt; and &lt;a href=&quot;https://x.com/oktadev&quot;&gt;X&lt;/a&gt;, and subscribe to our &lt;a href=&quot;https://www.youtube.com/c/OktaDev/&quot;&gt;YouTube&lt;/a&gt; channel. Leave a comment below if you have any questions!&lt;/p&gt;
</description>
        <pubDate>Mon, 24 Aug 2026 00:00:00 -0500</pubDate>
        <link>https://developer.okta.com/blog/2026/08/24/xaa-oidc-resource</link>
        <guid isPermaLink="true">https://developer.okta.com/blog/2026/08/24/xaa-oidc-resource</guid>
      </item>
    
      <item>
        <title>Add Cross App Access to Your OIDC Requesting Application</title>
        <description>&lt;p&gt;If you currently federate enterprise customers using OpenID Connect (OIDC) and want to connect with third-party applications, this Cross App Access (XAA) guide is for you.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/&quot;&gt;Identity Assertion Authorization Grant specification&lt;/a&gt;, the basis of XAA, was designed with OIDC in mind. Your app already holds an ID token after sign-in, but it’s the refresh token from that same sign-in that you exchange to reach a third-party app. This guide details what you need to support and how to make resource requests to a third-party app using XAA.&lt;/p&gt;

&lt;p&gt;&lt;strong class=&quot;hide&quot;&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#how-xaa-in-oidc-works&quot; id=&quot;markdown-toc-how-xaa-in-oidc-works&quot;&gt;How XAA in OIDC works&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#xaa-implementation-checklist-for-oidc-federated-applications&quot; id=&quot;markdown-toc-xaa-implementation-checklist-for-oidc-federated-applications&quot;&gt;XAA implementation checklist for OIDC-federated applications&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#request-the-id-jag-token&quot; id=&quot;markdown-toc-request-the-id-jag-token&quot;&gt;Request the ID-JAG token&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#request-the-access-token&quot; id=&quot;markdown-toc-request-the-access-token&quot;&gt;Request the access token&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#call-the-resource-api&quot; id=&quot;markdown-toc-call-the-resource-api&quot;&gt;Call the resource API&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#handle-token-expiration&quot; id=&quot;markdown-toc-handle-token-expiration&quot;&gt;Handle token expiration&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#making-cross-application-requests-from-your-oidc-app-securely&quot; id=&quot;markdown-toc-making-cross-application-requests-from-your-oidc-app-securely&quot;&gt;Making cross-application requests from your OIDC app securely&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#configure-your-xaa-oidc-requesting-app-in-okta&quot; id=&quot;markdown-toc-configure-your-xaa-oidc-requesting-app-in-okta&quot;&gt;Configure your XAA OIDC requesting app in Okta&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#register-the-requesting-app-in-okta&quot; id=&quot;markdown-toc-register-the-requesting-app-in-okta&quot;&gt;Register the requesting app in Okta&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#register-the-test-resource-app-in-okta&quot; id=&quot;markdown-toc-register-the-test-resource-app-in-okta&quot;&gt;Register the test resource app in Okta&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#register-your-requesting-app-at-xaadev&quot; id=&quot;markdown-toc-register-your-requesting-app-at-xaadev&quot;&gt;Register your requesting app at xaa.dev&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#register-and-configure-the-ai-agent-in-okta&quot; id=&quot;markdown-toc-register-and-configure-the-ai-agent-in-okta&quot;&gt;Register and configure the AI Agent in Okta&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#validate-the-xaa-connection-end-to-end&quot; id=&quot;markdown-toc-validate-the-xaa-connection-end-to-end&quot;&gt;Validate the XAA connection end-to-end&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#learn-more-about-cross-app-access-oidc-and-oauth-20&quot; id=&quot;markdown-toc-learn-more-about-cross-app-access-oidc-and-oauth-20&quot;&gt;Learn more about Cross App Access, OIDC, and OAuth 2.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;how-xaa-in-oidc-works&quot;&gt;How XAA in OIDC works&lt;/h2&gt;

&lt;p&gt;When an agent (like one running in Claude) needs API access, it presents an &lt;strong&gt;Identity Assertion Authorization Grant (ID-JAG)&lt;/strong&gt;. The ID-JAG is a short-lived JSON Web Token (JWT) issued by the Identity Provider (IdP) for your app’s user. You exchange the ID-JAG token for an access token to the resource application you’re connecting with.&lt;/p&gt;

&lt;p&gt;The sequence diagram below describes the OIDC XAA flow and how your application fits into it. You’ll handle the flow in two parts: where your application requests the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ID-JAG&lt;/code&gt; from the IdP using the refresh token, and where your app requests the access token from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ID-JAG&lt;/code&gt; from the third-party resource app’s authorization server.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/xaa-oidc-requesting/xaa-oidc-sequence-diagram-9434e0a44bb3267295acbb2937d216602154205bbe0119b48bf2a9da4aa512d5.svg&quot; alt=&quot;Sequence diagram showing OIDC sign-in between the user and Okta IdP, a token exchange producing an ID-JAG, and then requests an access token from the ID-JAG to call the API.&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;xaa-implementation-checklist-for-oidc-federated-applications&quot;&gt;XAA implementation checklist for OIDC-federated applications&lt;/h2&gt;

&lt;p&gt;Follow the guide in this section to support XAA in your OIDC application when your app connects to a third-party resource application. The XAA flow places the burden of token validation onto the IdP and the resource app’s authorization server. The initial sign-in step remains the same; however, you’ll add two requests before getting an access token to make the resource API call.&lt;/p&gt;

&lt;p&gt;Once the user completes signing in, you’ll:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Request the ID-JAG token from Okta using the refresh token&lt;/li&gt;
  &lt;li&gt;Request the OAuth access token from the third-party resource app’s OAuth authorization server&lt;/li&gt;
  &lt;li&gt;Use the access token to make the API request to the resource app&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;request-the-id-jag-token&quot;&gt;Request the ID-JAG token&lt;/h3&gt;

&lt;p&gt;Your OIDC application runs the authorization code flow as usual, requesting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;offline_access&lt;/code&gt; scope, and receives an ID token and a refresh token at the callback. The ID token is still there for your app’s session, but the refresh token is the credential for the exchange, so hold on to it for the session.&lt;/p&gt;

&lt;p&gt;When your application needs resources from a third-party app, exchange the refresh token for an ID-JAG at Okta’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/token&lt;/code&gt; endpoint. This exchange follows the &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8693&quot;&gt;OAuth 2.0 Token Exchange (RFC 8693)&lt;/a&gt; mechanism. Authenticate the request with the client credentials from your OIDC app using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_secret_post&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;POST&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;/oauth2/v1/token&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;your-okta-domain.okta.com&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/x-www-form-urlencoded&lt;/span&gt;

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&amp;amp;
subject_token=&amp;lt;the refresh token from sign-in&amp;gt;&amp;amp;
subject_token_type=urn:ietf:params:oauth:token-type:refresh_token&amp;amp;
requested_token_type=urn:ietf:params:oauth:token-type:id-jag&amp;amp;
audience=&amp;lt;the resource app's authorization server issuer URI&amp;gt;&amp;amp;
resource=&amp;lt;the resource app's API base URL&amp;gt;&amp;amp;
scope=&amp;lt;the resource app's required scopes&amp;gt;&amp;amp;
client_id=&amp;lt;your_client_id&amp;gt;&amp;amp;
client_secret=&amp;lt;your_client_secret&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The IdP responds with a short-lived, signed JSON Web Token (JWT), the ID-JAG, as the value in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt; property within the payload.&lt;/p&gt;

&lt;h3 id=&quot;request-the-access-token&quot;&gt;Request the access token&lt;/h3&gt;

&lt;p&gt;Redeem the ID-JAG at the resource app’s authorization server’s token endpoint. The resource app’s authorization server handles the redemption request, not the IdP. This request uses the &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7523&quot;&gt;JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants (RFC 7523)&lt;/a&gt;. The ID-JAG is the assertion now, and you include the scopes required for the resource request. The scope matches the scopes requested in the ID-JAG request. The HTTP request looks something like:&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;POST&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;/token&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;the-resource-server.example.com&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/x-www-form-urlencoded&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Basic &amp;lt;base64(resource_as_client_id:resource_as_client_secret)&amp;gt;&lt;/span&gt;

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&amp;amp;
assertion=&amp;lt;ID-JAG token&amp;gt;&amp;amp;
scope=todos.read
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the example HTTP request, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization&lt;/code&gt; header uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Basic&lt;/code&gt; scheme as a demo. Use the authorization scheme required by the resource application.&lt;/p&gt;

&lt;p&gt;The response payload contains the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt; property, whose value is the access token.&lt;/p&gt;

&lt;h3 id=&quot;call-the-resource-api&quot;&gt;Call the resource API&lt;/h3&gt;

&lt;p&gt;Use the access token to make the API resource request by using the value in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization&lt;/code&gt; header. For example:&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;/api/todos/&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;the-resource-server.example.com&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Bearer &amp;lt;access_token&amp;gt;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Accept&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;handle-token-expiration&quot;&gt;Handle token expiration&lt;/h3&gt;

&lt;p&gt;ID-JAG tokens have a short timeline by design. When it expires, request a new ID-JAG using the same refresh token. Refresh tokens are long-lived, so you don’t need to send the user back to sign in again for every ID-JAG. Refresh tokens eventually expire or get revoked, too. If the IdP rejects it (you’ll see an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalid_grant&lt;/code&gt; error), send the user through sign-in again to get a fresh one.&lt;/p&gt;

&lt;h2 id=&quot;making-cross-application-requests-from-your-oidc-app-securely&quot;&gt;Making cross-application requests from your OIDC app securely&lt;/h2&gt;

&lt;p&gt;With these steps complete, you’ve configured your OIDC application for Cross App Access. Your app can now obtain access tokens for third-party resource apps, using the federation you already run.&lt;/p&gt;

&lt;p&gt;You can now use Okta to make cross-application requests from your OIDC app to third-party resource apps.&lt;/p&gt;

&lt;p&gt;Before you configure anything, here’s who runs what in this walkthrough:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Component&lt;/th&gt;
      &lt;th&gt;Role&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Your requesting app and its code&lt;/td&gt;
      &lt;td&gt;Runs on your infrastructure. The application XAA let’s reach third-party resource apps&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Your Okta org&lt;/td&gt;
      &lt;td&gt;The IdP. Authenticates your users and issues the ID-JAG&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;The Okta app integration representing your requesting app&lt;/td&gt;
      &lt;td&gt;A registration in Okta, not a running service. Let’s your users sign in and let’s Okta issue ID-JAGs on your app’s behalf&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;The Okta app integration representing the test resource app&lt;/td&gt;
      &lt;td&gt;A registration in Okta, not a running service. Holds the XAA configuration Okta uses to mint ID-JAGs for xaa.dev’s resource authorization server&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;xaa.dev’s resource authorization server&lt;/td&gt;
      &lt;td&gt;Runs on xaa.dev’s infrastructure, not yours. Mints and validates the access tokens the test resource app accepts&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;xaa.dev’s resource API server&lt;/td&gt;
      &lt;td&gt;Plays the test resource app for this walkthrough, running against your requesting app&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;xaa.dev stands in for the third-party resource app so you can verify the requesting side, which is the only side you’re actually building. Ownership here is the reverse of the companion resource-app post: there, xaa.dev played the requesting side while you built the resource side; here, xaa.dev plays the resource side while you build the requesting side.&lt;/p&gt;

&lt;h2 id=&quot;configure-your-xaa-oidc-requesting-app-in-okta&quot;&gt;Configure your XAA OIDC requesting app in Okta&lt;/h2&gt;

&lt;p&gt;Let’s configure your OIDC requester application in Okta. Before you begin, you’ll need:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Values from your app (we’ll walk through the values needed below)&lt;/li&gt;
  &lt;li&gt;An Okta Integrator Free Plan account. &lt;a href=&quot;https://developer.okta.com/signup/&quot;&gt;Sign up for a new account&lt;/a&gt; to test out the XAA feature&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://xaa.dev/&quot;&gt;xaa.dev&lt;/a&gt; for testing your requesting app&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;register-the-requesting-app-in-okta&quot;&gt;Register the requesting app in Okta&lt;/h3&gt;

&lt;p&gt;Create the Okta app integration representing your requesting app by following the guide’s &lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#supported-requesting-apps&quot;&gt;OIDC app creation steps&lt;/a&gt;, using these tutorial-specific values:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;App integration name&lt;/strong&gt;: “Requesting App”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Grant type&lt;/strong&gt;: Authorization Code, plus &lt;strong&gt;Refresh Token&lt;/strong&gt; under &lt;strong&gt;Core Grants&lt;/strong&gt;. You’ll need a Refresh Token to request the ID-JAG&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Sign-in redirect URIs&lt;/strong&gt;: the callback URL of your requesting app, e.g., “https://requester-app-uri/callback”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Sign-out redirect URIs&lt;/strong&gt;: the sign-out URL of your requesting app&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Assignments&lt;/strong&gt;: keep &lt;strong&gt;Skip group assignments for now&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After creating the app integration, copy the &lt;strong&gt;Client ID&lt;/strong&gt; and &lt;strong&gt;Client secret&lt;/strong&gt; from the &lt;strong&gt;General&lt;/strong&gt; tab and add them to your requesting app. Your app uses these same credentials for sign-in and for the ID-JAG token exchange.&lt;/p&gt;

&lt;p&gt;Assign your test user to the requesting app integration. See &lt;a href=&quot;https://help.okta.com/okta_help.htm?type=oie&amp;amp;id=ext-lcm-assign-app-user&quot;&gt;Assign an app integration to a user&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With your requesting app integration registered, you’ll need an Okta app integration representing the test resource app next.&lt;/p&gt;

&lt;h3 id=&quot;register-the-test-resource-app-in-okta&quot;&gt;Register the test resource app in Okta&lt;/h3&gt;

&lt;p&gt;Create the Okta app integration representing the test resource app by following the guide’s &lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#supported-resource-apps&quot;&gt;OIDC app creation steps&lt;/a&gt;, using these tutorial-specific values:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;App integration name&lt;/strong&gt;: “Resource App for Testing”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Sign-in redirect URIs&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://idp.xaa.dev/oidc-resource/callback&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Assignments&lt;/strong&gt;: keep &lt;strong&gt;Skip group assignments for now&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;⚠️ &lt;strong&gt;Note:&lt;/strong&gt; These values are for setup only; they don’t constitute a working single sign-on (SSO) connection to the test resource app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Assign your test user to this app integration the same way you did for the requesting app integration above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource Server extra configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#enable-xaa-on-a-custom-app-integration&quot;&gt;Enable Cross App Access (XAA) on this app integration&lt;/a&gt; from the &lt;strong&gt;Resource Server&lt;/strong&gt; tab, and configure:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Issuer URL&lt;/strong&gt;: Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://auth.resource.xaa.dev&lt;/code&gt;. This is xaa.dev’s resource authorization server, not yours. Entering it here tells Okta which external server to mint ID-JAGs for&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Audience/tenant ID&lt;/strong&gt;: This is optional and not needed for this walkthrough&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;⚠️ &lt;strong&gt;Note:&lt;/strong&gt; Use Audience/tenant ID when you have multiple tenants in your organization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/xaa-oidc-requesting/resource-app-config-c02b3f07771e0c9cc784d651972cea249e5693938b7d476f962aecfe9f71d05a.jpg&quot; alt=&quot;Resource Server tab showing Cross-app access (XAA) enabled with Issuer URL configured.&quot; width=&quot;1200&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With the test resource app integration configured in Okta, register your requesting app at xaa.dev next.&lt;/p&gt;

&lt;h3 id=&quot;register-your-requesting-app-at-xaadev&quot;&gt;Register your requesting app at xaa.dev&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href=&quot;https://xaa.dev/developer/test-requesting-app?tab=oidc&quot;&gt;Test your requesting app&lt;/a&gt;. Add your &lt;strong&gt;IdP issuer URL&lt;/strong&gt; as your Okta Integrator account ID (i.e., https://your-okta-domain.okta.com). Put your email into the &lt;strong&gt;Test user identifier&lt;/strong&gt;, for example, name1234…@okta.com. After you enter all values, click &lt;strong&gt;Register&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Registering here registers your requesting app as an OAuth client at xaa.dev’s resource authorization server, and returns a &lt;strong&gt;Client ID&lt;/strong&gt; and &lt;strong&gt;Client Secret&lt;/strong&gt;. Save them. Use this &lt;strong&gt;Client ID&lt;/strong&gt; in the AI agent’s resource connection in the next section.&lt;/p&gt;

&lt;p&gt;When you get to the token exchange later in this walkthrough, substitute these values:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;audience&lt;/code&gt;&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://auth.resource.xaa.dev&lt;/code&gt;, xaa.dev’s resource authorization server (AS) issuer URL&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope&lt;/code&gt;&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;todos.read&lt;/code&gt;, the scope required to call the test resource app’s API&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource&lt;/code&gt;&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://api.resource.xaa.dev&lt;/code&gt;, xaa.dev’s resource API base URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep this site open; you’ll return to it to run the validation flow after the Okta configuration is complete.&lt;/p&gt;

&lt;h3 id=&quot;register-and-configure-the-ai-agent-in-okta&quot;&gt;Register and configure the AI Agent in Okta&lt;/h3&gt;

&lt;p&gt;With your requesting app integration and the test resource app integration configured, register a new AI Agent in Okta. During registration, you’ll link your requesting app integration under &lt;strong&gt;User access and authentication&lt;/strong&gt;, register the agent’s own OAuth client, and then connect the test resource app integration as a &lt;strong&gt;Resource Connection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Register the AI Agent and link your requesting app integration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#register-ai-agent-user-access-and-authentication&quot;&gt;Register the AI Agent and link its requesting app integration&lt;/a&gt;. Use these tutorial-specific values:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Name&lt;/strong&gt;: “Agent”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Allow users to access this agent&lt;/strong&gt;: &lt;strong&gt;Select an existing app&lt;/strong&gt;, then choose your requesting app integration (e.g., “Requesting App”)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Add owners&lt;/strong&gt;: assign your test user as an individual owner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This linked app integration is what your users sign in through to reach the agent: they authenticate against it, and the agent then acts on their behalf.&lt;/p&gt;

&lt;p&gt;Open the AI Agent you created, then go to the &lt;strong&gt;Client registration&lt;/strong&gt; tab: you’ll see the agent uses the same credentials as the linked requesting app integration. You’ll use this client ID and secret to request an ID-JAG from the IdP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect the test resource app integration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#configure-the-xaa-connection&quot;&gt;Add a resource connection to the AI agent&lt;/a&gt;. Use these tutorial-specific values:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Application instance&lt;/strong&gt;: “Resource App for Testing”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AI agent’s client ID registered in this app&lt;/strong&gt;: the &lt;strong&gt;Client ID&lt;/strong&gt; from xaa.dev (it should look similar to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byora...&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scopes&lt;/strong&gt;: &lt;strong&gt;Allow any scope&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Activate the AI Agent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Activating the linked requesting app integration usually activates the AI Agent. If the agent’s status is &lt;strong&gt;STAGED&lt;/strong&gt;, &lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#activate-the-ai-agent&quot;&gt;activate it manually&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once the AI Agent is active, the configuration is complete. Except for Machine access, all checkmarks on the agent configuration page must be green.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/xaa-oidc-requesting/ai-agent-completion-d4c4c371231eeba749b906291759fd592df14ba77757a2483cac1796b7e00b42.jpg&quot; alt=&quot;AI Agent configuration page showing all required sections completed with green checkmarks except Machine access.&quot; width=&quot;1200&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;validate-the-xaa-connection-end-to-end&quot;&gt;Validate the XAA connection end-to-end&lt;/h3&gt;

&lt;p&gt;Once the Okta setup is complete, sign in to your requesting app and trigger whatever action in your code path calls the test resource app, for example, a button that fetches its data. That single action should exercise all three checklist requests in sequence: your app requests the ID-JAG, redeems it for an access token at xaa.dev’s resource authorization server, and calls the test resource app’s API with that token.&lt;/p&gt;

&lt;p&gt;Then return to &lt;a href=&quot;https://xaa.dev/developer/test-requesting-app?tab=oidc&quot;&gt;xaa.dev&lt;/a&gt; and open the &lt;strong&gt;Live verification&lt;/strong&gt; tab. A green &lt;strong&gt;Conformance passed&lt;/strong&gt; panel appears. This confirms all steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Auth Server accepted your ID-JAG&lt;/li&gt;
  &lt;li&gt;An access token was issued&lt;/li&gt;
  &lt;li&gt;Resource Server accepted your access token&lt;/li&gt;
  &lt;li&gt;The API call to /api/todos/ was successful&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the companion resource-app post, xaa.dev acts as the client, and you observe the result. Here, your requesting app acts as the client, and xaa.dev observes it.&lt;/p&gt;

&lt;p&gt;At this stage, the JSON conformance log has the complete details of the XAA flow. You can either download the log or share a URL with your IdP.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/xaa-oidc-requesting/requesting-conformance-log-success-c085d2269b9cb138bab3cf0764e45d507b992cbca5fb9aeae57b7cc5a4f2c284.jpg&quot; alt=&quot;xaa.dev Live verification tab showing a green Conformance passed panel after a successful XAA flow.&quot; width=&quot;1200&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;learn-more-about-cross-app-access-oidc-and-oauth-20&quot;&gt;Learn more about Cross App Access, OIDC, and OAuth 2.0&lt;/h2&gt;

&lt;p&gt;If this guide helped you implement Cross App Access with OIDC, explore these resources:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;📘 &lt;a href=&quot;https://help.okta.com/oie/en-us/content/topics/apps/apps-cross-app-access.htm&quot;&gt;Cross App Access Documentation&lt;/a&gt;: Official guides for configuring and managing Cross App Access in production&lt;/li&gt;
  &lt;li&gt;🎙️ &lt;a href=&quot;https://www.youtube.com/watch?v=qKs4k5Y1x_s&quot;&gt;Developer Podcast on MCP and Cross App Access&lt;/a&gt;: Hear the backstory, use cases, and why this matters for developers&lt;/li&gt;
  &lt;li&gt;📋 &lt;a href=&quot;/blog/2026/07/06/submit-oin-xaa&quot;&gt;How to Build and List Secure Cross App Access (XAA) Connections on Okta Integration Network (OIN)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Identity 101:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.okta.com/identity-101/whats-the-difference-between-oauth-openid-connect-and-saml/&quot;&gt;What’s the Difference Between OAuth, OpenID Connect, and SAML?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.okta.com/en-in/identity-101/saml-vs-oauth/&quot;&gt;What are SAML, OAuth, and OIDC?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.okta.com/identity-101/why-you-should-migrate-to-oauth-2-0-from-static-api-tokens/&quot;&gt;Why You Should Migrate to OAuth 2.0 From Static API Tokens&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/07/27/enterprise-ready-getting-started&quot;&gt;How to Get Going with the On-Demand SaaS Apps Workshops&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow us on &lt;a href=&quot;https://www.linkedin.com/company/oktadev&quot;&gt;LinkedIn&lt;/a&gt; and &lt;a href=&quot;https://x.com/oktadev&quot;&gt;X&lt;/a&gt;, and subscribe to our &lt;a href=&quot;https://www.youtube.com/c/OktaDev/&quot;&gt;YouTube&lt;/a&gt; channel. Leave a comment below if you have any questions!&lt;/p&gt;
</description>
        <pubDate>Fri, 21 Aug 2026 00:00:00 -0500</pubDate>
        <link>https://developer.okta.com/blog/2026/08/21/xaa-oidc-requesting</link>
        <guid isPermaLink="true">https://developer.okta.com/blog/2026/08/21/xaa-oidc-requesting</guid>
      </item>
    
      <item>
        <title>I Found My Coordinates: Code, Community, and Okta</title>
        <description>&lt;p&gt;It all started with a phone call from an institute that reached out to recruit me for its campus ambassador program. I was navigating my first year of college, figuring things out. It was all new for me, a new city, far from home, and a hostel room. I was not aware of anything except books and lectures.&lt;/p&gt;

&lt;p&gt;A senior at my campus ambassador program advised me to create a LinkedIn account and start looking for opportunities from day one. As they say, “You don’t have to be great to start, but you have to start to be great.” So I started applying for random internships. Soon, I received my first offer, where I discovered the power of networking and community.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“No one can whistle a symphony. It takes a whole orchestra to play it.”&lt;/em&gt; – H.E. Luccock&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Community has always been the best part of my life. The love and support we receive from each other are invaluable. I have always given to the community and gained far more in return.&lt;/p&gt;

&lt;p&gt;I attended an institution that wasn’t particularly well-known. There weren’t many communities, so why not do something about it? Through professional platforms, I discovered Google Developer Student Clubs (GDSC), researched the opportunity, and applied. I became the pioneer GDSC Lead at my institution, and one of 280 leads across India.&lt;/p&gt;

&lt;p&gt;We built the community from scratch, organizing meetups, workshops, and hackathons that delivered real value to students. These experiences sparked curiosity and helped students find their voice. When I stumbled across the Developer Advocate role, I was very happy and curious, so I started digging into it.&lt;/p&gt;

&lt;h2 id=&quot;my-journey-into-advocacy&quot;&gt;My journey into advocacy&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“What you seek is seeking you.”&lt;/em&gt; - Rumi&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One day, I got an email from a founder who said he liked my LinkedIn profile and wanted me to join his organization as a Developer Advocate – that moment was surreal. That’s how I landed an internship as a Developer Advocate at Zuplo, where I learned something crucial: to understand developers’ pain points, I needed to walk in their shoes. To truly advocate for developers, I needed to be a developer myself.&lt;/p&gt;

&lt;p&gt;Wanting to experience the world of technology from the inside led me to join JPMorgan as a Software Development Engineer, where I got to experience their work firsthand, hear about their challenges, and understand their breakthroughs and victories.&lt;/p&gt;

&lt;p&gt;Each project and colleague shaped my perspective. By the end, I didn’t just understand developers, I was one of them.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/vanshika-intro-blog/talks-982d3b93901bdaf48af54aae2870ecd3ca95ff0d7b6fbebd235806a8eb57b6e9.jpg&quot; alt=&quot;Vanshika working with developer communities&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;building-impact-and-recognition&quot;&gt;Building impact and recognition&lt;/h2&gt;
&lt;p&gt;Over the past few years, I delivered various sessions at leading Indian colleges, mentored over 10,000 students, and built a community of more than 600,000 tech enthusiasts. Every session sparked curiosity, every mentorship helped shape someone’s path.&lt;/p&gt;

&lt;p&gt;I collaborated with brands I admired, while enterprises, founders, and the Government of India recognized my work with a National Award. Magazines, articles, and podcasts also featured my efforts. At that moment, I knew this was my calling to make my professional life an extension of what I love doing.&lt;/p&gt;

&lt;p&gt;I remember one time I went to a college for a session where I was the only woman on a panel speaking to nearly 3,000 students. Afterwards, several women approached me, nervous, excited, and full of questions. One said, “Seeing you on that stage inspired us. If you can do it, we can too.”&lt;/p&gt;

&lt;p&gt;That moment stayed with me. I understand that this is the work that matters. These conversations remind me why I do this work every single day; it’s worth it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/vanshika-intro-blog/community-bd7cff8b6b07c9c80760997ca6f153639ee670dd9ee8b33d15183c0b495632ef.jpg&quot; alt=&quot;Community and meeting with people&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-next-chapter-at-okta&quot;&gt;The next chapter at Okta&lt;/h2&gt;
&lt;p&gt;When I saw that Okta – an organization I genuinely admired – was hiring, I applied immediately. I’m eternally grateful that I did.&lt;/p&gt;

&lt;p&gt;There is something surreal about writing this: I am joining Okta as a Senior Builder Advocate. I am writing this sentence, and my brain is doing a thing where it’s not sure if this is real life.&lt;/p&gt;

&lt;p&gt;Yes, Builder Advocate, because in today’s AI era, we don’t need to be traditional developers to create products and bring ideas to life. With the right tools and platforms, anyone can build and advocate not just for developers, but for builders of all kinds. Builders today aren’t just coders.&lt;/p&gt;

&lt;p&gt;They’re AI builders, vibecoders, LLMs/agents, no-code and low-code builders, and developers, all creating real value. Okta is here for all of us.&lt;/p&gt;

&lt;p&gt;This is the kind of role I once admired from the outside and thought, “This is where all my pieces fit.” I’m grateful to be here.&lt;/p&gt;

&lt;p&gt;I’m excited for the journey ahead. Okta’s mission to secure every interaction deeply resonates with me. I’m committed to bringing my passion for community and advocacy to this role, listening to builders, understanding their challenges, and helping them succeed.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/vanshika-intro-blog/okta-d55a62ff41373baebbe3b62e82507c31cce6c68687a079039f13734ca5420dbd.jpg&quot; alt=&quot;Vanshika at Okta&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Let’s connect. Whether you are a developer, a builder, or curious about identity and access management, I’d love to build together. That’s what Okta is about. That’s what I’m here to do. Come talk to me on &lt;a href=&quot;https://www.x.com/vanshikatech&quot;&gt;X&lt;/a&gt;, &lt;a href=&quot;https://www.linkedin.com/in/vanshikapandeyy&quot;&gt;LinkedIn&lt;/a&gt;, or &lt;a href=&quot;https://github.com/Vanshikapandey30&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s to new beginnings and the incredible community we’re building together at Okta.&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;
</description>
        <pubDate>Thu, 20 Aug 2026 00:00:00 -0500</pubDate>
        <link>https://developer.okta.com/blog/2026/08/20/vanshika-intro-blog</link>
        <guid isPermaLink="true">https://developer.okta.com/blog/2026/08/20/vanshika-intro-blog</guid>
      </item>
    
      <item>
        <title>Build a Flask App with Okta for Secure OIDC Login and Authorized API Calls</title>
        <description>&lt;p&gt;Python syntax and the flexibility of the Flask microframework make it a popular choice for quickly building web applications. While Flask provides the essentials to get you started, you’ll need to tackle two critical pieces yourself: secure user authentication and authorization for your backend services. After all, how do you securely sign users into your application? And once they’re signed in, how does your app fetch data from a backend service that only serves authorized requests?&lt;/p&gt;

&lt;p&gt;This tutorial shows you how to solve both. You’ll build a Flask dashboard app that signs users in with Okta using OpenID Connect (OIDC). Once signed in, the app uses the resulting OAuth 2.0 access token to call a separate backend API that responds only to authorized requests. In this common pattern, a web app needs to fetch data from a protected backend service. You’ll use Authlib, an OIDC client library, to configure the Authorization Code flow with Proof Key for Code Exchange (PKCE) automatically. You’ll also learn how to add a custom scope to the access token and validate it on the backend to control what data the API returns.&lt;/p&gt;

&lt;p&gt;Check out the complete source code on &lt;a href=&quot;https://github.com/oktadev/okta-flask-oauth-example&quot;&gt;GitHub&lt;/a&gt; and get started without setting it up from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong class=&quot;hide&quot;&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#build-a-flask-app-with-oidc-authentication&quot; id=&quot;markdown-toc-build-a-flask-app-with-oidc-authentication&quot;&gt;Build a Flask app with OIDC authentication&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#create-an-app-integration-in-the-okta-admin-console&quot; id=&quot;markdown-toc-create-an-app-integration-in-the-okta-admin-console&quot;&gt;Create an app integration in the Okta Admin Console&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#enable-self-service-user-registration&quot; id=&quot;markdown-toc-enable-self-service-user-registration&quot;&gt;Enable self-service user registration&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#add-secure-user-login-to-a-flask-application-with-okta&quot; id=&quot;markdown-toc-add-secure-user-login-to-a-flask-application-with-okta&quot;&gt;Add secure user login to a Flask application with Okta&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#create-a-basic-flask-web-application&quot; id=&quot;markdown-toc-create-a-basic-flask-web-application&quot;&gt;Create a basic Flask web application&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#configure-flask-environment-variables&quot; id=&quot;markdown-toc-configure-flask-environment-variables&quot;&gt;Configure Flask environment variables&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#implement-the-oidc-authentication-logic&quot; id=&quot;markdown-toc-implement-the-oidc-authentication-logic&quot;&gt;Implement the OIDC authentication logic&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#run-the-flask-oauth-web-app&quot; id=&quot;markdown-toc-run-the-flask-oauth-web-app&quot;&gt;Run the Flask OAuth web app&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#call-a-protected-api-with-oauth-scoped-tokens&quot; id=&quot;markdown-toc-call-a-protected-api-with-oauth-scoped-tokens&quot;&gt;Call a protected API with OAuth scoped tokens&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#build-a-protected-users-api&quot; id=&quot;markdown-toc-build-a-protected-users-api&quot;&gt;Build a protected users API&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#extend-the-flask-app-to-interact-with-the-resource-server&quot; id=&quot;markdown-toc-extend-the-flask-app-to-interact-with-the-resource-server&quot;&gt;Extend the Flask app to interact with the resource server&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#update-the-flask-ui-to-display-protected-data&quot; id=&quot;markdown-toc-update-the-flask-ui-to-display-protected-data&quot;&gt;Update the Flask UI to display protected data&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#test-the-protected-api-integration&quot; id=&quot;markdown-toc-test-the-protected-api-integration&quot;&gt;Test the protected API integration&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#learn-more-about-oauth-and-oidc&quot; id=&quot;markdown-toc-learn-more-about-oauth-and-oidc&quot;&gt;Learn more about OAuth and OIDC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;build-a-flask-app-with-oidc-authentication&quot;&gt;Build a Flask app with OIDC authentication&lt;/h2&gt;

&lt;p&gt;In this tutorial, you’ll build a simple dashboard application and learn how to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Securely sign users in to view their profile information using Okta as the OpenID Connect identity provider&lt;/li&gt;
  &lt;li&gt;Use Authlib to configure the Authorization Code flow with &lt;a href=&quot;https://oauth.net/2/pkce/&quot;&gt;PKCE&lt;/a&gt; automatically, following OAuth 2.1 security best practices&lt;/li&gt;
  &lt;li&gt;Add a feature for new users to self-register for an account directly from the sign-in page&lt;/li&gt;
  &lt;li&gt;Enable authenticated users to interact with a protected API using access tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.python.org/&quot;&gt;Python&lt;/a&gt; 3.14 or later and &lt;a href=&quot;https://pypi.org/project/pip/&quot;&gt;pip&lt;/a&gt; installed&lt;/li&gt;
  &lt;li&gt;An &lt;a href=&quot;https://developer.okta.com/signup/&quot;&gt;Okta Integrator Free Plan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;create-an-app-integration-in-the-okta-admin-console&quot;&gt;Create an app integration in the Okta Admin Console&lt;/h2&gt;

&lt;p&gt;Before you begin, you’ll need an Okta Integrator Free Plan account. To get one, sign up for an &lt;a href=&quot;https://developer.okta.com/login&quot;&gt;Integrator account&lt;/a&gt;. Once you have an account, sign in to your &lt;a href=&quot;https://developer.okta.com/login&quot;&gt;Integrator account&lt;/a&gt;. Next, in the Admin Console:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Go to &lt;strong&gt;Applications and Resources&lt;/strong&gt; &amp;gt; &lt;strong&gt;Applications&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Click &lt;strong&gt;Create App Integration&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;In the &lt;strong&gt;Sign in Method&lt;/strong&gt; section, select &lt;strong&gt;OIDC - OpenID Connect&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Select &lt;strong&gt;Web Application&lt;/strong&gt; as the application type, then click &lt;strong&gt;Next&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Enter an app integration name&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Configure the redirect URIs:
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Sign-in redirect URIs&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:5000/authorization-code/callback&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Sign-out redirect URIs&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:5000&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;In the &lt;strong&gt;Assignments&lt;/strong&gt; section, assign a group or keep the default &lt;strong&gt;Everyone&lt;/strong&gt; group assigned for all&lt;/li&gt;
  &lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;details&gt;
  &lt;summary&gt;Where are my new app's credentials?&lt;/summary&gt;

&lt;p&gt;Creating an OIDC Web App manually in the Admin Console configures your Okta Org with the application settings.&lt;/p&gt;

&lt;p&gt;After creating the app, you can find the configuration details on the app’s &lt;strong&gt;General&lt;/strong&gt; tab:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Client ID&lt;/strong&gt;: Found in the &lt;strong&gt;Client Credentials&lt;/strong&gt; section&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Client Secret&lt;/strong&gt;: Click &lt;strong&gt;Show&lt;/strong&gt; in the &lt;strong&gt;Client Credentials&lt;/strong&gt; section to reveal&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Issuer&lt;/strong&gt;: Found in the &lt;strong&gt;Issuer URI&lt;/strong&gt; field for the authorization server that appears by selecting &lt;strong&gt;Security&lt;/strong&gt; &amp;gt; &lt;strong&gt;API&lt;/strong&gt; from the navigation pane.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ll need these values for your application configuration:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;OKTA_OAUTH2_ISSUER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://dev-133337.okta.com/oauth2/default&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OKTA_OAUTH2_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0oab8eb55Kb9jdMIr5d6&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OKTA_OAUTH2_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;NEVER-SHOW-SECRETS&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Your Okta domain is the first part of your issuer, before &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/oauth2/default&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You can also use the &lt;a href=&quot;https://github.com/okta/okta-cli-client&quot;&gt;Okta CLI Client&lt;/a&gt; or &lt;a href=&quot;https://github.com/okta/okta-powershell-cli&quot;&gt;Okta PowerShell Module&lt;/a&gt; to automate this process. See &lt;a href=&quot;https://developer.okta.com/docs/guides/sign-into-web-app/-/create-okta-application/&quot;&gt;this guide&lt;/a&gt; for more information about setting up your app.&lt;/p&gt;

&lt;/details&gt;

&lt;h2 id=&quot;enable-self-service-user-registration&quot;&gt;Enable self-service user registration&lt;/h2&gt;

&lt;p&gt;User registration is vital for any application, and Okta makes the process quick and hassle-free. See &lt;a href=&quot;https://help.okta.com/oie/en-us/content/topics/identity-engine/policies/about-ssr.htm&quot;&gt;Self-Service Registration&lt;/a&gt; for an overview of how it works. Setting this up involves two main steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create a user profile policy – This policy defines the attributes a user must provide when self-registering. See the &lt;a href=&quot;https://help.okta.com/oie/en-us/content/topics/identity-engine/policies/create-profile-enrollment-policy.htm&quot;&gt;Okta documentation&lt;/a&gt; for detailed instructions.&lt;/li&gt;
  &lt;li&gt;Assign your application to the policy – This step is required; without it, the self-service registration flow doesn’t activate for your app. See the &lt;a href=&quot;https://help.okta.com/oie/en-us/content/topics/identity-engine/policies/select-profile-enrollment-policy.htm&quot;&gt;product documentation&lt;/a&gt; for more details.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;add-secure-user-login-to-a-flask-application-with-okta&quot;&gt;Add secure user login to a Flask application with Okta&lt;/h2&gt;

&lt;p&gt;Now that you’ve configured your app integration in Okta and noted the required Okta application details, it’s time to create your Flask application.&lt;/p&gt;

&lt;h3 id=&quot;create-a-basic-flask-web-application&quot;&gt;Create a basic Flask web application&lt;/h3&gt;

&lt;p&gt;First, you’ll create a project folder and a Python virtual environment, then install the necessary dependencies.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create a project folder named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flask-web-app&lt;/code&gt; and a subfolder called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;venv&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Open a terminal window in the project folder and create a Python virtual environment&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; venv venv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Activate the virtual environment. Use the command that corresponds to your operating system.&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# On macOS/Linux&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;venv/bin/activate

&lt;span class=&quot;c&quot;&gt;# On Windows&lt;/span&gt;
venv&lt;span class=&quot;se&quot;&gt;\S&lt;/span&gt;cripts&lt;span class=&quot;se&quot;&gt;\a&lt;/span&gt;ctivate.bat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;With your environment active, install the required libraries:&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;Authlib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;1.6.1 Flask[async]&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;3.1.1 python-dotenv&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;1.1.1 &lt;span class=&quot;nv&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;2.32.4 okta-jwt-verifier&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;0.3.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;configure-flask-environment-variables&quot;&gt;Configure Flask environment variables&lt;/h3&gt;

&lt;p&gt;To keep sensitive credentials out of source control, you’ll store them in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file and load them into the application’s configuration. This practice prevents hard-coding secrets directly into your source code. Make sure to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt; file to prevent accidentally committing your credentials to source control.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Create a file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; in the project root and add your Okta application’s configuration details. Replace the placeholders with your actual values.&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;OKTA_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;yourClientId&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OKTA_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;yourClientSecret&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OKTA_BASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;https://&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;yourOktaDomain&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.py&lt;/code&gt; file in the project root. This file reads the values from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; and constructs the OIDC metadata URL, which the application uses to fetch endpoints from Okta.&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;OKTA_CLIENT_ID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'OKTA_CLIENT_ID'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;OKTA_CLIENT_SECRET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'OKTA_CLIENT_SECRET'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;OKTA_BASE_URL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;OKTA_BASE_URL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;OKTA_METADATA_URL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OKTA_BASE_URL&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/.well-known/openid-configuration&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;implement-the-oidc-authentication-logic&quot;&gt;Implement the OIDC authentication logic&lt;/h3&gt;

&lt;p&gt;Next, create the main application file, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.py&lt;/code&gt;, in your project’s root directory. This file holds the core logic for handling OIDC authentication. Paste the following code into it to configure the basic application.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;flask&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Flask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;authlib.integrations.flask_client&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OAuth&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;dotenv&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;load_dotenv&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;requests&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;load_dotenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Flask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;secret_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;urandom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'config'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'index.html'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;initialize-the-okta-oidc-client&quot;&gt;Initialize the Okta OIDC client&lt;/h4&gt;

&lt;p&gt;To set up the connection to Okta, add the following code to the bottom of your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.py&lt;/code&gt; file. This code initializes an OAuth object and registers the Okta application as a remote provider.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Create a registry with an OAuth object
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;oauth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OAuth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Register a remote application
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;oauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'okta'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'OKTA_CLIENT_ID'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client_secret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'OKTA_CLIENT_SECRET'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;server_metadata_url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'OKTA_METADATA_URL'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client_kwargs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;'scope'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'openid profile email offline_access'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;'code_challenge_method'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'S256'&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first parameter in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register()&lt;/code&gt; method (in this case, &lt;em&gt;Okta&lt;/em&gt;) is the name of the remote application. You’ll later access the remote application with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oauth.okta&lt;/code&gt; to handle all the OIDC-related functions.&lt;/p&gt;

&lt;p&gt;The client_kwargs dictionary passes extra parameters to the authorization request. Because the code_challenge_method is present, the Authlib library automatically uses the &lt;a href=&quot;https://oauth.net/2/pkce/&quot;&gt;PKCE&lt;/a&gt; flow, which enhances security.&lt;/p&gt;

&lt;h4 id=&quot;build-flask-routes-for-oidc-authentication&quot;&gt;Build Flask routes for OIDC authentication&lt;/h4&gt;

&lt;p&gt;The sign-in process begins when the user clicks the Login button, which redirects the browser to the Okta-hosted Sign-In page.&lt;/p&gt;

&lt;p&gt;After the user signs in, Okta redirects the browser to the configured sign-in redirect URI with an authorization code. Authlib then exchanges that code for tokens and automatically calls Okta’s userinfo endpoint to retrieve the user’s profile. Similarly, after a user signs out, Okta redirects the browser to the sign-out redirect URI.&lt;/p&gt;

&lt;p&gt;Let’s start by creating a simple HTML page that allows the user to log in. This page displays a sign-in button for signed-out users and a sign-out button for logged-in users, along with profile information.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/login&lt;/code&gt; route to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.py&lt;/code&gt; file. This endpoint serves as the entry point for the authentication flow. It constructs the authorization redirect URI and sends the user’s browser to the Okta-hosted Sign-In page to begin the OIDC process.&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'/login'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;redirect_uri&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'authorization_code_callback'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_external&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;okta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authorize_redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;redirect_uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Next, implement the callback handler for the Redirect URI you configured in your Okta application (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:5000/authorization-code/callback&lt;/code&gt;). This handler processes the authorization code returned by Okta. Calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authorize_access_token()&lt;/code&gt; triggers Authlib to exchange the code for tokens and automatically call Okta’s userinfo endpoint. The callback then stores both the userinfo and the full token in Flask’s session for use across requests.&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'/authorization-code/callback'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;authorization_code_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;okta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authorize_access_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'loggedInUser'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'userinfo'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'token'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/logout&lt;/code&gt; route. This endpoint clears the local user session, signing the user out of the Flask application. It then constructs a redirect URL to Okta’s logout_endpoint, which terminates the user’s session with Okta, ensuring a complete and secure sign-out.&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'/logout'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;logout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'token'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;id_token_hint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;id_token_hint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'id_token'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;okta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server_metadata&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logout_endpoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'end_session_endpoint'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'loggedInUser'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'token'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logout_endpoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logout_url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logout_endpoint&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;?'&lt;/span&gt;
            &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'id_token_hint=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id_token_hint&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;amp;'&lt;/span&gt;
            &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'post_logout_redirect_uri=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;index&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_external&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logout_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Finally, update the root route (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;). This route now checks the session for a signed-in user and passes that information to the template.&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;loggedInUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'loggedInUser'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'index.html'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loggedInUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With the back-end routes in place, you can now create the HTML template files to provide a user interface for signing in, signing out, and displaying profile data.&lt;/p&gt;

&lt;p&gt;Step 1: Create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;templates&lt;/code&gt; directory in your project root. Inside it, create an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.html&lt;/code&gt; file. This page displays a Login button for signed-out users and, for signed-in users, profile information with a Logout button.&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Home&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ url_for('static', filename='css/index.css') }}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;wrapper&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;nav&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;main-nav&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;role=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;navigation&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;aria-label=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Primary navigation&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav-left&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Flask Web App&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav-right&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          {% if user %}
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/fetch-users&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Fetch Users&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/logout&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
          {% else %}
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/login&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Login&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
          {% endif %}
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;

      &lt;span class=&quot;nt&quot;&gt;&amp;lt;main&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;container&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        {% if user %}
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;section&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;aria-label=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;User welcome&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user-info&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Welcome, {{ user.name or user.preferred_username or 'User' }}!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You have successfully logged in. You can now fetch and view user details.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Click the button below to load the list of users.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;onclick=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;location.href='/fetch-users'&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Fetch Users&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;

        {% else %}
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;section&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;aria-label=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Login prompt&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user-info&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Welcome to the Flask Web App&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;If you are seeing this, you are not logged in yet.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This app uses OpenID Connect (OIDC) for secure login. After logging in, you can access user-specific data.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Click the login button below to get started.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/login&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;button-link&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Login / Register&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
        {% endif %}
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Step 2: For styling, create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static/css&lt;/code&gt; directory in your project root. Inside it, create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.css&lt;/code&gt; and add the following code.&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;100%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;font-family&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&quot;Segoe UI&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tahoma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Geneva&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Verdana&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sans-serif&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;line-height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;overflow-y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;hidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;nav&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.main-nav&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#2f2f2f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#f0f0f0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;justify-content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;space-between&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;align-items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;nav&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.main-nav&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#f0f0f0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;margin-left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1.5rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;600&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;nav&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.main-nav&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:hover&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#bfbfbf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;underline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.wrapper&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;flex-direction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;min-height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;100vh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1000px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.container&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;flex-direction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;section&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.user-info&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;transparent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1.5rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;margin-bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#222222&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.button-link&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#1a73e8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#fff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.6rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1.3rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inline-block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;margin-top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;600&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:hover&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;.button-link&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:hover&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#155ab6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#fff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;run-the-flask-oauth-web-app&quot;&gt;Run the Flask OAuth web app&lt;/h3&gt;

&lt;p&gt;You’re ready to test the sign-in and sign-out functionality.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Start the application with the Flask server.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;flask run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;Open a new terminal window and start the resource server.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;flask &lt;span class=&quot;nt&quot;&gt;--app&lt;/span&gt; resource-server.py run &lt;span class=&quot;nt&quot;&gt;--port&lt;/span&gt; 5001
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;Open a browser and go to http://localhost:5000.&lt;/li&gt;
  &lt;li&gt;Click &lt;strong&gt;Login&lt;/strong&gt;. The browser redirects you to the Okta Sign-In Widget.&lt;/li&gt;
  &lt;li&gt;After you sign in, Okta redirects you back to your application, and the page displays the signed-in user’s details.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/flask-oauth-web-app/flask-app-after-login-a26fa84d54cf98534da58634e2acf7c22ef6d282e055c102cfe03a0f45bd549e.jpg&quot; alt=&quot;Flask web app home page showing a welcome message after successful login with a Fetch Users button&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Click &lt;strong&gt;Logout&lt;/strong&gt;. The browser clears your session and returns you to the home page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ve successfully added a secure authentication flow to your Flask app. Next, you’ll extend it to call a protected API using scoped tokens.&lt;/p&gt;

&lt;h2 id=&quot;call-a-protected-api-with-oauth-scoped-tokens&quot;&gt;Call a protected API with OAuth scoped tokens&lt;/h2&gt;

&lt;p&gt;Using OAuth for API authorization is a modern security best practice. It allows you to use access tokens to interact with APIs to fetch and manage data securely. This section walks you through how to use a scoped access token to fetch data from your own resource server.&lt;/p&gt;

&lt;p&gt;Every action on an endpoint that supports OAuth 2.0 requires a specific scope. In this example, your endpoint requires the specific scope &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api:read-users&lt;/code&gt; to return a list of sample users.&lt;/p&gt;

&lt;p&gt;To include this scope in the tokens minted from Okta, follow these steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;In your Okta Admin Console, navigate to &lt;strong&gt;Security&lt;/strong&gt; &amp;gt; &lt;strong&gt;API&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Select your &lt;strong&gt;Authorization Server&lt;/strong&gt; and go to the &lt;strong&gt;Scopes&lt;/strong&gt; tab&lt;/li&gt;
  &lt;li&gt;Click the &lt;strong&gt;Add Scope&lt;/strong&gt; button and enter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api:read-users&lt;/code&gt; in the &lt;strong&gt;Name&lt;/strong&gt; field&lt;/li&gt;
  &lt;li&gt;Optionally, enter the &lt;strong&gt;Description&lt;/strong&gt; and the &lt;strong&gt;Display Phrase&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Click &lt;strong&gt;Create&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, when your app requests this scope, Okta includes it in the tokens it issues.&lt;/p&gt;

&lt;p&gt;Before creating the resource server, add an extra layer of security. You want your endpoint only to consume tokens intended for your server, preventing attackers from replaying or misusing tokens issued for other services.&lt;/p&gt;

&lt;p&gt;To do this, restrict the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aud&lt;/code&gt; claim that Okta returns in the access token to your resource server. To achieve this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;In your Okta Admin Console, navigate to &lt;strong&gt;Security&lt;/strong&gt; &amp;gt; &lt;strong&gt;API&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Select your &lt;strong&gt;Authorization Server&lt;/strong&gt;. In the &lt;strong&gt;Settings&lt;/strong&gt; tab, click &lt;strong&gt;Edit&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Change the &lt;strong&gt;Audience&lt;/strong&gt; field to &lt;a href=&quot;http://localhost:5001&quot;&gt;http://localhost:5001&lt;/a&gt; – this is where your resource server runs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;build-a-protected-users-api&quot;&gt;Build a protected users API&lt;/h3&gt;

&lt;p&gt;For this demo app, you’ll create a minimal resource server with a single route called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch-users&lt;/code&gt; that returns a list of sample users.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Start by modifying the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file. Add the variable that stores the domain of your resource server (in this case, http://localhost:5001):&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;OKTA_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;yourClientId&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OKTA_CLIENT_SECRET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;yourClientSecret&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OKTA_BASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;https://&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;yourOktaDomain&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;RESOURCE_SERVER_BASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;http://localhost:5001
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Now, modify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.py&lt;/code&gt; file to include the resource server URL and the list of sample users. Replace the existing code with the following:&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;OKTA_CLIENT_ID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'OKTA_CLIENT_ID'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;OKTA_CLIENT_SECRET&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'OKTA_CLIENT_SECRET'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;OKTA_BASE_URL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;OKTA_BASE_URL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;OKTA_METADATA_URL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OKTA_BASE_URL&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/.well-known/openid-configuration&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;RESOURCE_SERVER_BASE_URL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;RESOURCE_SERVER_BASE_URL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;SAMPLE_USERS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Alice Johnson&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;firstName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;lastName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Johnson&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;alice.johnson@example.com&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Bob Smith&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;firstName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Bob&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;lastName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Smith&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bob.smith@example.com&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Nina Patel&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;firstName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Nina&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;lastName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Patel&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;nina.patel@example.com&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resource-server.py&lt;/code&gt; file in the project directory and add the following code:&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;flask&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Flask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jsonify&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;okta_jwt_verifier&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BaseJWTVerifier&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Flask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'config'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ISSUER&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'OKTA_BASE_URL'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CLIENT_ID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'OKTA_CLIENT_ID'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;AUDIENCE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'RESOURCE_SERVER_BASE_URL'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;REQUIRED_SCOPES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;api:read-users&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;verify_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pass&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'/fetch-users'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;is_valid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error_response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;verify_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_valid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error_response&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jsonify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'SAMPLE_USERS'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;The endpoint returns sample users if the token is valid. Next, you’ll add token validation.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Now write the logic for token validation. You’ll validate the token’s issuer, audience, and client ID against your authorization server URL, resource server domain, and client ID. You’ll also verify that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api:read-users&lt;/code&gt; scope is present in the token’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scp&lt;/code&gt; claim.&lt;/p&gt;

    &lt;p&gt;Start with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verify_token&lt;/code&gt; function. First, check if the Authorization header is of type Bearer:&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;auth_header&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auth_header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jsonify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;No token provided&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;parts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auth_header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bearer&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jsonify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Authorization header must be Bearer token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;Next, use Okta’s package to verify that the audience, issuer, and client ID in the token match your configuration:&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;jwt_verifier&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BaseJWTVerifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;issuer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ISSUER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CLIENT_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;audience&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AUDIENCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jwt_verifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;verify_access_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Token verification failed: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jsonify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Invalid or Expired Token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;Now add scope validation. Extract the claims from the token and check that the custom scope &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api:read-users&lt;/code&gt; is present in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scp&lt;/code&gt; claim. If the token doesn’t contain this scope, reject it and return an error. In the try block, before the return statement, add the following code:&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;claims&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jwt_verifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;token_scopes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;claims&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;scp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;REQUIRED_SCOPES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;issubset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token_scopes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jsonify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Insufficient Scope&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;403&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Token is Valid!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;Once everything is done, your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verify_token&lt;/code&gt; function looks like this:&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;verify_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;auth_header&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auth_header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jsonify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;No token provided&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;parts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;auth_header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bearer&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jsonify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Authorization header must be Bearer token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;jwt_verifier&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BaseJWTVerifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;issuer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ISSUER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CLIENT_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;audience&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AUDIENCE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jwt_verifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;verify_access_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;claims&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jwt_verifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;token_scopes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;claims&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;scp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;REQUIRED_SCOPES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;issubset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token_scopes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jsonify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Insufficient Scope&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;403&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Token is Valid!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Token verification failed: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jsonify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Invalid or Expired Token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;extend-the-flask-app-to-interact-with-the-resource-server&quot;&gt;Extend the Flask app to interact with the resource server&lt;/h3&gt;

&lt;p&gt;With the Okta configuration complete, the next step is to update your Flask application to request this scope. This ensures that the access token issued to the user includes the necessary permission to call the protected endpoint.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;In your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.py&lt;/code&gt; file, find the oauth.register block and add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api:read-users&lt;/code&gt; to the scope string within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_kwargs&lt;/code&gt; dictionary.&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;n&quot;&gt;oauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
     &lt;span class=&quot;c1&quot;&gt;# ... other parameters
&lt;/span&gt;     &lt;span class=&quot;n&quot;&gt;client_kwargs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;s&quot;&gt;'scope'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'openid profile email offline_access api:read-users'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;s&quot;&gt;'code_challenge_method'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'S256'&lt;/span&gt;
     &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Add a new route, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/fetch-users&lt;/code&gt;, to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.py&lt;/code&gt; file. This endpoint uses the stored access token from the user’s session to make a secure GET request to the protected endpoint in the resource server. If successful, it passes the returned list of users to the index.html template for display.&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'/fetch-users'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetchUsers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'token'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;loggedInUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'loggedInUser'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loggedInUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'login'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;access_token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'access_token'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
       
    &lt;span class=&quot;n&quot;&gt;RESOURCE_SERVER_BASE_URL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'RESOURCE_SERVER_BASE_URL'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Bearer &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;access_token&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RESOURCE_SERVER_BASE_URL&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/fetch-users&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'index.html'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loggedInUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;API request failed: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status_code&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status_code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Update the root route (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;) to initialize the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;userList&lt;/code&gt; variable in the template. This prevents errors when the page first loads before any data is fetched. Replace the existing index function with the following code.&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
     &lt;span class=&quot;n&quot;&gt;loggedInUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'loggedInUser'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'index.html'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loggedInUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;update-the-flask-ui-to-display-protected-data&quot;&gt;Update the Flask UI to display protected data&lt;/h3&gt;

&lt;p&gt;Now, let’s modify the front end to add a button that triggers the API call and a table to display the returned user data.&lt;/p&gt;

&lt;p&gt;Step 1: Open the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;templates/index.html&lt;/code&gt; file and replace its entire contents with the updated code below. This version adds a Fetch Users button for signed-in users and a table to display the user list when available.&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Home&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ url_for('static', filename='css/index.css') }}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;wrapper&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;nav&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;main-nav&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;role=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;navigation&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;aria-label=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Primary navigation&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav-left&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Flask Web App&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nav-right&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          {% if user %}
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/fetch-users&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Fetch Users&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/logout&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
          {% else %}
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/login&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Login&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
          {% endif %}
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;

      &lt;span class=&quot;nt&quot;&gt;&amp;lt;main&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;container&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        {% if user %}
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;section&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;aria-label=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;User welcome&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user-info&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Welcome, {{ user.name or 'User' }}!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You have successfully logged in. You can now fetch and view user details.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Click the button below to load the list of users.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;onclick=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;location.href='/fetch-users'&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Fetch Users&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;

        {% if userList %}
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;section&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;aria-label=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Users table&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Users List&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;table-wrapper&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;nt&quot;&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Email&lt;span class=&quot;nt&quot;&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;nt&quot;&gt;&amp;lt;th&amp;gt;&lt;/span&gt;First Name&lt;span class=&quot;nt&quot;&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;nt&quot;&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Last Name&lt;span class=&quot;nt&quot;&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;table-body-scroll&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
                  {% for user in userList %}
                  &lt;span class=&quot;nt&quot;&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ user.email }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ user.firstName }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{ user.lastName }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
                  {% endfor %}
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
        {% endif %} {% else %}
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;section&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;aria-label=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Login prompt&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user-info&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Welcome to the Flask Web App&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;If you are seeing this, you are not logged in yet.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This app uses OpenID Connect (OIDC) for secure login. After logging in, you can access user-specific data.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Click the login button below to get started.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/login&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;button-link&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Login / Register&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
        {% endif %}
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Step 2: To style the new user table, open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static/css/index.css&lt;/code&gt; file and add the following CSS at the end of the file.&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;.table-body-scroll&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;max-height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;300px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;overflow-y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#d1d9e6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;box-shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;6px&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 
  &lt;span class=&quot;nt&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border-collapse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;collapse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;100%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#fff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#2c3e50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8px&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;table-layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 
  &lt;span class=&quot;nt&quot;&gt;thead&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#e9f0fb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#1a3c72&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;700&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.75rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border-bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#d1d9e6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sticky&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 
  &lt;span class=&quot;nt&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.75rem&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border-bottom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;#e2e8f0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1rem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;word-wrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;break-word&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;test-the-protected-api-integration&quot;&gt;Test the protected API integration&lt;/h3&gt;

&lt;p&gt;Ready to see it all work? Make sure both servers are running: the Flask app (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flask run&lt;/code&gt;) and the resource server (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flask --app resource-server.py run --port 5001&lt;/code&gt;). Open a browser, go to http://localhost:5000, sign in, and then click Fetch Users. A table appears, listing sample users returned from your resource server. If you see the user data, your integration is a success!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/flask-oauth-web-app/flask-app-fetch-users-a7068b43529b0895a27496ddb58b82686f1bfd39f5bbd47609c2fff1bb489e7a.jpg&quot; alt=&quot;Flask web app displaying a users table with email, first name, and last name columns after successfully fetching data from the protected API&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Your Flask application now has secure user authentication and can use an access token to interact with a protected API.&lt;/p&gt;

&lt;h2 id=&quot;learn-more-about-oauth-and-oidc&quot;&gt;Learn more about OAuth and OIDC&lt;/h2&gt;

&lt;p&gt;If you’d like to learn more about the concepts covered in this tutorial, explore these official Okta resources:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/docs/guides/sign-into-web-app-redirect/python/main/&quot;&gt;Sign users in to your web app&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/docs/guides/implement-grant-type/authcodepkce/main/&quot;&gt;Authorization Code flow with PKCE&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/docs/guides/validate-access-tokens/python/main/&quot;&gt;Validate Access Tokens&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/docs/guides/customize-authz-server/main/&quot;&gt;Create an Authorization Server&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2025/07/28/express-oauth-pkce&quot;&gt;Secure a Node Express App with OAuth 2.0 and PKCE&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember to follow us on &lt;a href=&quot;https://www.linkedin.com/company/oktadev&quot;&gt;LinkedIn&lt;/a&gt;, &lt;a href=&quot;https://x.com/oktadev&quot;&gt;X&lt;/a&gt;, and subscribe to our &lt;a href=&quot;https://www.youtube.com/c/OktaDev/&quot;&gt;YouTube channel&lt;/a&gt; for more exciting content. We also want to hear from you about the topics you’d like to see and any questions you may have. Leave us a comment below!&lt;/p&gt;

</description>
        <pubDate>Tue, 28 Jul 2026 00:00:00 -0500</pubDate>
        <link>https://developer.okta.com/blog/2026/07/28/flask-oauth-web-app</link>
        <guid isPermaLink="true">https://developer.okta.com/blog/2026/07/28/flask-oauth-web-app</guid>
      </item>
    
      <item>
        <title>Enable Your SAML Requesting App for Cross App Access</title>
        <description>&lt;p&gt;If you currently federate enterprise customers using Security Assertion Markup Language (SAML) and want to connect with third-party applications without migrating to OpenID Connect (OIDC), this Cross App Access (XAA) guide is for you.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/&quot;&gt;Identity Assertion Authorization Grant specification&lt;/a&gt;, the basis of XAA, was originally designed with OIDC in mind. To use it in SAML applications, you must accommodate specific security and uniqueness requirements. This guide details what you need to support and how to make resource requests to a third-party app using XAA.&lt;/p&gt;

&lt;p&gt;&lt;strong class=&quot;hide&quot;&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#how-xaa-in-saml-works&quot; id=&quot;markdown-toc-how-xaa-in-saml-works&quot;&gt;How XAA in SAML works&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#xaa-implementation-checklist-for-saml-federated-applications&quot; id=&quot;markdown-toc-xaa-implementation-checklist-for-saml-federated-applications&quot;&gt;XAA implementation checklist for SAML-federated applications&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#request-the-refresh-token&quot; id=&quot;markdown-toc-request-the-refresh-token&quot;&gt;Request the refresh token&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#request-the-id-jag-token&quot; id=&quot;markdown-toc-request-the-id-jag-token&quot;&gt;Request the ID-JAG token&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#request-the-access-token&quot; id=&quot;markdown-toc-request-the-access-token&quot;&gt;Request the access token&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#handle-token-expiration&quot; id=&quot;markdown-toc-handle-token-expiration&quot;&gt;Handle token expiration&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#making-cross-application-requests-from-your-saml-app-securely&quot; id=&quot;markdown-toc-making-cross-application-requests-from-your-saml-app-securely&quot;&gt;Making cross-application requests from your SAML app securely&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#configure-your-xaa-saml-requesting-app-in-okta&quot; id=&quot;markdown-toc-configure-your-xaa-saml-requesting-app-in-okta&quot;&gt;Configure your XAA SAML Requesting App in Okta&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#register-and-configure-the-ai-agent-in-okta&quot; id=&quot;markdown-toc-register-and-configure-the-ai-agent-in-okta&quot;&gt;Register and configure the AI Agent in Okta&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#test-the-saml-20-app&quot; id=&quot;markdown-toc-test-the-saml-20-app&quot;&gt;Test the SAML 2.0 app&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#validate-the-xaa-connection-end-to-end&quot; id=&quot;markdown-toc-validate-the-xaa-connection-end-to-end&quot;&gt;Validate the XAA connection end-to-end&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#learn-more-about-cross-app-access-saml-and-oauth-20&quot; id=&quot;markdown-toc-learn-more-about-cross-app-access-saml-and-oauth-20&quot;&gt;Learn more about Cross App Access, SAML, and OAuth 2.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;how-xaa-in-saml-works&quot;&gt;How XAA in SAML works&lt;/h2&gt;

&lt;p&gt;When an agent (like one running in Claude) needs API access, it presents an &lt;strong&gt;Identity Assertion Authorization Grant (ID-JAG)&lt;/strong&gt;. The ID-JAG is a short-lived JSON Web Token (JWT) issued by the Identity Provider (IdP) for your app’s user. You exchange the ID-JAG token for an access token to the resource application you’re connecting with.&lt;/p&gt;

&lt;p&gt;The sequence diagram shown below describes the SAML XAA flow and how your application fits in. You’ll handle the flow in two parts: where your application requests the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ID-JAG&lt;/code&gt; from the IdP using a refresh token, and where your app requests the access token from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ID-JAG&lt;/code&gt; from the third-party resource app’s authorization server.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/xaa-saml-requester/xaa-saml-sequence-diagram-6bc6e11c736b50ad2fb90bae87c966218b33dca67112dc408a9a1faacf299967.svg&quot; alt=&quot;Sequence diagram showing SAML SSO between the user and Okta IdP, two OAuth token exchanges producing a refresh token and then an ID-JAG, and then requests an access token from the ID-JAG to call the API.&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;xaa-implementation-checklist-for-saml-federated-applications&quot;&gt;XAA implementation checklist for SAML-federated applications&lt;/h2&gt;

&lt;p&gt;Follow the guide in this section to support XAA in your SAML application when your app connects to a third-party resource application. The XAA flow places the burden of token validation onto the IdP and the resource app’s authorization server. The initial SSO step remains the same; however, you’ll add three requests before getting an access token to make the resource API call.&lt;/p&gt;

&lt;p&gt;Once the user completes signing in, you’ll:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Request an OAuth refresh token from Okta&lt;/li&gt;
  &lt;li&gt;Request the ID-JAG token from Okta&lt;/li&gt;
  &lt;li&gt;Request the OAuth access token from the third-party resource app’s OAuth authorization server&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;request-the-refresh-token&quot;&gt;Request the refresh token&lt;/h3&gt;

&lt;p&gt;Your SAML application makes the initial SSO handshake and handles the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SAMLResponse&lt;/code&gt; as usual. The ACS handler verifies the incoming payload and its digital signature, then extracts the underlying XML assertion. This assertion serves as the required credential for the subsequent exchange.&lt;/p&gt;

&lt;p&gt;Immediately after validating the SAML response, in the same ACS request before redirecting the user onward, you’ll perform the first exchange. Base64-encode the assertion you extracted, then exchange it for a refresh token using Okta’s token endpoint. This exchange follows the &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8693&quot;&gt;OAuth 2.0 Token Exchange (RFC 8693)&lt;/a&gt; mechanism.&lt;/p&gt;

&lt;p&gt;This step requires a credential in the request. Because you’re making a sensitive resource request, Okta requires a JWT signed with an asymmetric key pair as the credential – a private key JWT.&lt;/p&gt;

&lt;p&gt;Follow the instructions in the &lt;a href=&quot;https://developer.okta.com/docs/guides/build-self-signed-jwt/js/main/&quot;&gt;Build a JWT for Client Authentication&lt;/a&gt; for your tech stack. The signed JWT becomes a client assertion in the request.&lt;/p&gt;

&lt;p&gt;The refresh token HTTP request looks like this:&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;POST&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;/oauth2/v1/token&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;your-okta-domain.okta.com&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/x-www-form-urlencoded&lt;/span&gt;

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&amp;amp;
subject_token=&amp;lt;base64url-encoded SAML assertion XML&amp;gt;&amp;amp;
subject_token_type=urn:ietf:params:oauth:token-type:saml2&amp;amp;
requested_token_type=urn:ietf:params:oauth:token-type:refresh_token&amp;amp;
scope=openid+offline_access&amp;amp;
client_id=&amp;lt;your_client_id&amp;gt;&amp;amp;
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&amp;amp;
client_assertion=&amp;lt;your signed JWT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The IdP (Okta) responds with the refresh token. The refresh token is opaque and long-lived. Persist the refresh token for the session; never persist or use the SAML assertion past this exchange.&lt;/p&gt;

&lt;h3 id=&quot;request-the-id-jag-token&quot;&gt;Request the ID-JAG token&lt;/h3&gt;

&lt;p&gt;With the refresh token in hand, you have the credentials to request the ID-JAG token when your application needs resources from a third-party app. This exchange uses the same OAuth token exchange mechanism as the first step. You make the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt; request to the same endpoint, Okta IdP, using the refresh token, and request the ID-JAG token type.&lt;/p&gt;

&lt;p&gt;This request also requires a signed JWT as it’s requesting a sensitive resource.&lt;/p&gt;

&lt;p&gt;The HTTP request looks like this:&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;POST&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;/oauth2/v1/token&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;your-okta-domain.okta.com&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/x-www-form-urlencoded&lt;/span&gt;

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&amp;amp;
subject_token=&amp;lt;the refresh_token from prior step&amp;gt;&amp;amp;
subject_token_type=urn:ietf:params:oauth:token-type:refresh_token&amp;amp;
requested_token_type=urn:ietf:params:oauth:token-type:id-jag&amp;amp;
audience=&amp;lt;the resource app's authorization server issuer URI&amp;gt;&amp;amp;
resource=&amp;lt;the resource app's API base URL&amp;gt;&amp;amp;
scope=&amp;lt;the resource app's required scopes&amp;gt;&amp;amp;
client_id=&amp;lt;your_client_id&amp;gt;&amp;amp;
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&amp;amp;
client_assertion=&amp;lt;your signed JWT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The IdP responds with a short-lived, signed JSON Web Token (JWT), the ID-JAG, as the value in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt; property within the payload.&lt;/p&gt;

&lt;h3 id=&quot;request-the-access-token&quot;&gt;Request the access token&lt;/h3&gt;

&lt;p&gt;Redeem the ID-JAG at the resource app’s authorization server’s token endpoint. The resource app’s authorization server handles the redemption request, not the IdP. This request uses the &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7523&quot;&gt;JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants (RFC 7523)&lt;/a&gt;. The ID-JAG is the assertion now, and you include the scopes required for the resource request. The scope matches the scopes requested in the ID-JAG request. The HTTP request looks something like:&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;POST&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;/token&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;the-resource-server.example.com&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/x-www-form-urlencoded&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Basic &amp;lt;base64(resource_as_client_id:resource_as_client_secret)&amp;gt;&lt;/span&gt;

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&amp;amp;
assertion=&amp;lt;ID-JAG token&amp;gt;&amp;amp;
scope=todos.read
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In the example HTTP request, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization&lt;/code&gt; header uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Basic&lt;/code&gt; scheme as a demo. Use the authorization scheme required by the resource application.&lt;/p&gt;

&lt;p&gt;The response payload contains the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt; property, whose value is the access token. Use the access token to make the API resource request by using the value in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization&lt;/code&gt; header. For example:&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;Authorization: Bearer &amp;lt;access_token&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;handle-token-expiration&quot;&gt;Handle token expiration&lt;/h3&gt;

&lt;p&gt;ID-JAG tokens have a short timeline by design. When it expires, request a shiny new ID-JAG from the refresh token. Refresh tokens also have a lifespan. If the IdP rejects the refresh token due to expiration (you’ll see an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalid_grant&lt;/code&gt; error), then you need to obtain a new refresh token by having the user sign in via SSO again.&lt;/p&gt;

&lt;h2 id=&quot;making-cross-application-requests-from-your-saml-app-securely&quot;&gt;Making cross-application requests from your SAML app securely&lt;/h2&gt;

&lt;p&gt;With these steps complete, your SAML application is configured for Cross App Access. Agents can now authorize requests against your API while maintaining your existing production federation, eliminating the need for protocol migration.&lt;/p&gt;

&lt;p&gt;You can now use Okta to make cross-application requests with your SAML app.&lt;/p&gt;

&lt;h2 id=&quot;configure-your-xaa-saml-requesting-app-in-okta&quot;&gt;Configure your XAA SAML Requesting App in Okta&lt;/h2&gt;

&lt;p&gt;Let’s configure your SAML requester application in Okta. Before you begin, you’ll need:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Values from your app (we’ll walk through the values needed below)&lt;/li&gt;
  &lt;li&gt;An Okta Integrator Free Plan account. &lt;a href=&quot;https://developer.okta.com/signup/&quot;&gt;Sign up for a new account&lt;/a&gt; to test out the XAA feature&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://xaa.dev/&quot;&gt;xaa.dev&lt;/a&gt; for testing your requesting app&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;Cross App Access is an early access feature in Okta. New Integrator Free Plan account types include XAA support. If you have a paid Okta org plan and the following options are missing, contact your representative.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sign in to your Integrator Free Plan org and open the &lt;strong&gt;Admin Console&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.okta.com/docs/guides/create-an-app-integration/saml2/main/#create-a-custom-app-integration&quot;&gt;Create a custom SAML app integration&lt;/a&gt; representing your requesting app, using these values:&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;General Settings&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;App name&lt;/strong&gt;: “Requesting App”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In &lt;strong&gt;Configure SAML&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Single sign-on URL&lt;/strong&gt;: the ACS URL of your requester app, e.g., “https://requester-app-uri/saml/acs”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Audience URI (SP Entity ID)&lt;/strong&gt;: the SP Entity of your requester, e.g., “https://requester-app-uri/saml/metadata”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Name ID format&lt;/strong&gt;: select &lt;strong&gt;EmailAddress&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Application username&lt;/strong&gt;: select &lt;strong&gt;Email&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Update application username on&lt;/strong&gt;: select &lt;strong&gt;Create and update&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After creating the app, you’ll see more configuration options for your Okta SAML 2.0 requesting app. You’ll make changes in more than one tab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sign On configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the &lt;strong&gt;Sign On&lt;/strong&gt; tab, &lt;a href=&quot;https://developer.okta.com/docs/guides/create-an-app-integration/saml2/main/#create-a-custom-app-integration&quot;&gt;retrieve your SAML setup details&lt;/a&gt;: under &lt;strong&gt;View SAML setup instructions&lt;/strong&gt;, copy the &lt;strong&gt;Identity Provider Single Sign-On URL&lt;/strong&gt; and &lt;strong&gt;Identity Provider Issuer&lt;/strong&gt;, and download the X.509 certificate as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;idp-cert.pem&lt;/code&gt;. You’ll need all three in your requesting app setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assignments configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://help.okta.com/oie/en-us/content/topics/provisioning/lcm/lcm-assign-app-user.htm&quot;&gt;Assign your test user&lt;/a&gt; to the app on the &lt;strong&gt;Assignments&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;Now that we have the requesting app registered, it’s time to &lt;a href=&quot;https://developer.okta.com/docs/guides/create-an-app-integration/saml2/main/#create-a-custom-app-integration&quot;&gt;create a custom SAML app integration&lt;/a&gt; representing the resource app, using these values:&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;General Settings&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;App name&lt;/strong&gt;: “Resource App for Testing”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In &lt;strong&gt;Configure SAML&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Single sign-on URL&lt;/strong&gt;: “https://idp.xaa.dev/saml-resource/acs”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Audience URI (SP Entity ID)&lt;/strong&gt;: “https://idp.xaa.dev/saml-resource/metadata”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Name ID format&lt;/strong&gt;: select &lt;strong&gt;EmailAddress&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Application username&lt;/strong&gt;: select &lt;strong&gt;Email&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Update application username on&lt;/strong&gt;: select &lt;strong&gt;Create and update&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;Please note that we’re providing these for setup; they don’t constitute a working SSO connection to the resource app. Also, don’t assume the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NameID&lt;/code&gt; is an email address; it is whatever the customer’s SSO emits. Your matching set must remain consistent across your deployment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After creating the app, you’ll see more configuration options for your Okta SAML 2.0 app. You’ll make changes in the resource server tab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource Server extra configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#enable-xaa-on-a-custom-app-integration&quot;&gt;Enable Cross App Access (XAA)&lt;/a&gt; on the resource app integration from the &lt;strong&gt;Resource Server&lt;/strong&gt; tab, and configure:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Issuer URL&lt;/strong&gt;: https://auth.resource.xaa.dev&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Assignments configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://help.okta.com/oie/en-us/content/topics/provisioning/lcm/lcm-assign-app-user.htm&quot;&gt;Assign your test user&lt;/a&gt; to the app on the &lt;strong&gt;Assignments&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;The next step is to set up  &lt;a href=&quot;https://xaa.dev/&quot;&gt;xaa.dev&lt;/a&gt; for the resource app.&lt;/p&gt;

&lt;p&gt;Go to &lt;a href=&quot;https://xaa.dev/developer/test-requesting-app?tab=saml&quot;&gt;Test your requesting app&lt;/a&gt; and use these values:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;IdP issuer URL&lt;/strong&gt;: your Okta Integrator account ID (i.e., https://your-okta-domain.okta.com)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Test user identifier&lt;/strong&gt;: your email, e.g., name1234…@okta.com&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;SAML IdP entityID&lt;/strong&gt;: from the requesting app’s &lt;strong&gt;Sign On&lt;/strong&gt; tab &amp;gt; &lt;strong&gt;SAML Setup&lt;/strong&gt; &amp;gt; &lt;strong&gt;View SAML setup instructions&lt;/strong&gt;, the &lt;strong&gt;Identity Provider Issuer&lt;/strong&gt; value (i.e., http://www.okta.com/&lt;app-id&gt;)&lt;/app-id&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After all values are entered, click &lt;strong&gt;Register&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;register-and-configure-the-ai-agent-in-okta&quot;&gt;Register and configure the AI Agent in Okta&lt;/h3&gt;

&lt;p&gt;With your Okta SAML 2.0 requesting app and the resource app configured, register a new AI Agent in Okta. The AI Agent configuration defines the relationship between the Okta SAML 2.0 requesting app you created and the resource application, which in this case is xaa.dev. You configure credentials, link your requesting app during registration, and connect the resource app as a Resource Connection.&lt;/p&gt;

&lt;p&gt;In the Okta &lt;strong&gt;Admin Console&lt;/strong&gt;, &lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#register-ai-agent-user-access-and-authentication&quot;&gt;register the AI Agent and link your requesting app&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Navigate to &lt;strong&gt;Directory &amp;gt; AI Agents&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Select &lt;strong&gt;Register AI Agent &amp;gt; Register Manually&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Under &lt;strong&gt;Profile&lt;/strong&gt;, enter a &lt;strong&gt;Name&lt;/strong&gt;, e.g., “Agent”, and an optional description, then press &lt;strong&gt;Next&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Under &lt;strong&gt;User access and authentication &amp;gt; Allow users to access this agent&lt;/strong&gt;, select &lt;strong&gt;Select an existing app&lt;/strong&gt;, then choose the Okta SAML requesting app you created earlier (e.g., “Requesting App”). This app acts as the requesting app for the XAA flow: your users sign in to the agentic app through it, and the agent then acts on their behalf. Press &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Under &lt;strong&gt;owners&lt;/strong&gt;: assign your test user as an individual owner&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Select the AI Agent you just created to open its configuration. Configure the agent across the following tabs:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;On the &lt;strong&gt;Client registration&lt;/strong&gt; tab, &lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#register-ai-agent-user-access-and-authentication&quot;&gt;choose a client registration method&lt;/a&gt;:
    &lt;ol&gt;
      &lt;li&gt;Select the &lt;strong&gt;Public/private key&lt;/strong&gt; section&lt;/li&gt;
      &lt;li&gt;In the &lt;strong&gt;Define where keys are managed&lt;/strong&gt; section, choose &lt;strong&gt;Okta&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;Under the &lt;strong&gt;Add and manage keys&lt;/strong&gt; section, select &lt;strong&gt;Add public key&lt;/strong&gt;, then &lt;strong&gt;Generate new key&lt;/strong&gt;. Under &lt;strong&gt;PEM&lt;/strong&gt;, copy the private key into the &lt;strong&gt;Private key (PKCS8 PEM or private JWK)&lt;/strong&gt; field in your requesting app&lt;/li&gt;
      &lt;li&gt;Copy the &lt;strong&gt;KEY ID&lt;/strong&gt; and add it back to your requesting app&lt;/li&gt;
      &lt;li&gt;In the &lt;strong&gt;Provide Client ID to AI agent builder or developer&lt;/strong&gt; step, copy the &lt;strong&gt;Client ID&lt;/strong&gt;, and add it back to your requesting app&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;On the &lt;strong&gt;User access&lt;/strong&gt; tab
    &lt;ol&gt;
      &lt;li&gt;In the &lt;strong&gt;App used for access configuration&lt;/strong&gt;, select &lt;strong&gt;Select an existing SAML app&lt;/strong&gt;, then your &lt;strong&gt;SAML Application&lt;/strong&gt;, e.g., “Requesting App”&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;On the &lt;strong&gt;Resource connections&lt;/strong&gt; tab, &lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#configure-the-xaa-connection&quot;&gt;add a resource connection to the AI Agent&lt;/a&gt;. Use these values:
    &lt;ol&gt;
      &lt;li&gt;&lt;strong&gt;Application instance&lt;/strong&gt;: your resource app (e.g., “Resource App for Testing”)&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;AI agent’s client ID registered in this app&lt;/strong&gt;: the &lt;strong&gt;Client ID&lt;/strong&gt; from xaa.dev (it should look similar to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byora...&lt;/code&gt;)&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Scopes&lt;/strong&gt;: &lt;strong&gt;Allow any scope&lt;/strong&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.okta.com/docs/guides/xaa-agent-to-app/main/#activate-the-ai-agent&quot;&gt;Activate the AI Agent&lt;/a&gt;: activating the linked requesting app integration usually activates the AI Agent. If the agent’s status is &lt;strong&gt;STAGED&lt;/strong&gt;, go to the &lt;strong&gt;Actions&lt;/strong&gt; drop-down menu at the top and select &lt;strong&gt;Activate&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once the AI Agent is active, the configuration is complete. All checkmarks on the agent configuration page must be green.&lt;/p&gt;

&lt;h3 id=&quot;test-the-saml-20-app&quot;&gt;Test the SAML 2.0 app&lt;/h3&gt;

&lt;p&gt;At this stage, you are all set to run the requesting app and verify the complete XAA flow it is supposed to perform.&lt;/p&gt;

&lt;h3 id=&quot;validate-the-xaa-connection-end-to-end&quot;&gt;Validate the XAA connection end-to-end&lt;/h3&gt;

&lt;p&gt;Once the flow is complete in the SAML 2.0 application, return to &lt;a href=&quot;https://xaa.dev/&quot;&gt;xaa.dev&lt;/a&gt;. In the &lt;strong&gt;Live verification&lt;/strong&gt; tab, a green &lt;strong&gt;Conformance passed&lt;/strong&gt; panel appears. This confirms all steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Auth Server Accepted your ID-JAG&lt;/li&gt;
  &lt;li&gt;Access token was issued&lt;/li&gt;
  &lt;li&gt;Resource Server accepted your access token&lt;/li&gt;
  &lt;li&gt;API call to the /api/todos/ was a success.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this stage, the JSON conformance log will have the complete details of the XAA flow.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets-jekyll/blog/xaa-saml-requester/conformance-log-39e73c8a2b3b9fa66bd0a2ea098374dcd1602da53c6b5f06c4cd1db364626dba.jpg&quot; alt=&quot;conformance log&quot; width=&quot;800&quot; class=&quot;center-image&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;learn-more-about-cross-app-access-saml-and-oauth-20&quot;&gt;Learn more about Cross App Access, SAML, and OAuth 2.0&lt;/h2&gt;

&lt;p&gt;If this guide helped you implement Cross App Access with SAML, explore these resources:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;📘 &lt;a href=&quot;https://help.okta.com/oie/en-us/content/topics/apps/apps-cross-app-access.htm&quot;&gt;Cross App Access Documentation&lt;/a&gt;: Official guides for configuring and managing Cross App Access in production&lt;/li&gt;
  &lt;li&gt;🎙️ &lt;a href=&quot;https://www.youtube.com/watch?v=qKs4k5Y1x_s&quot;&gt;Developer Podcast on MCP and Cross App Access&lt;/a&gt;: Hear the backstory, use cases, and why this matters for developers&lt;/li&gt;
  &lt;li&gt;📋 &lt;a href=&quot;/blog/2026/07/06/submit-oin-xaa&quot;&gt;How to Build and List Secure Cross App Access (XAA) Connections on Okta Integration Network (OIN)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Identity 101:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.okta.com/identity-101/whats-the-difference-between-oauth-openid-connect-and-saml/&quot;&gt;What’s the Difference Between OAuth, OpenID Connect, and SAML?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.okta.com/en-in/identity-101/saml-vs-oauth/&quot;&gt;What are SAML, OAuth, and OIDC?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.okta.com/identity-101/why-you-should-migrate-to-oauth-2-0-from-static-api-tokens/&quot;&gt;Why You Should Migrate to OAuth 2.0 From Static API Tokens&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/07/27/enterprise-ready-getting-started&quot;&gt;How to Get Going with the On-Demand SaaS Apps Workshops&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow us on &lt;a href=&quot;https://www.linkedin.com/company/oktadev&quot;&gt;LinkedIn&lt;/a&gt; and &lt;a href=&quot;https://x.com/oktadev&quot;&gt;X&lt;/a&gt;, and subscribe to our &lt;a href=&quot;https://www.youtube.com/c/OktaDev/&quot;&gt;YouTube&lt;/a&gt; channel. Leave a comment below if you have any questions!&lt;/p&gt;

</description>
        <pubDate>Fri, 17 Jul 2026 00:00:00 -0500</pubDate>
        <link>https://developer.okta.com/blog/2026/07/17/xaa-saml-requester</link>
        <guid isPermaLink="true">https://developer.okta.com/blog/2026/07/17/xaa-saml-requester</guid>
      </item>
    
  </channel>
</rss>
