{"@attributes":{"version":"2.0"},"channel":{"title":"Okta Developer","description":"Secure, scalable, and highly available authentication and user management for any app.\n","link":"https:\/\/developer.okta.com","item":[{"title":"How to Build Low-Code API Integrations for Enterprise Apps Using Okta","description":"<p>API Integration Actions are now available in Okta Integration Network (OIN) for Integrator Free Trial Orgs to build Provisioning, Entitlements, and Universal Logout applications.<\/p>\n\n<h2 id=\"what-are-api-integration-actions\">What are API Integration Actions?<\/h2>\n\n<p>API Integration Actions are a feature that uses Workflows, Okta\u2019s low-code builder, to enable independent software vendors (ISVs) to build Okta Integrations (Provisioning, Entitlements, Universal Logout) that are seamlessly invoked by Okta services \u2014 for example, retrieving and updating entitlements or triggering risk-based logout flows.<\/p>\n\n<p>You can just skip the complexity of building and maintaining a System for Cross-domain Identity Management (SCIM) server. API Integration Actions allow you to use your existing APIs as-is by mapping them directly to Okta action contracts. By using our low-code builder, you no longer need in-depth knowledge of protocols, making it faster and easier to build, test, and deliver enterprise-grade Secure Identity Integrations. This leads to a fast time-to-value for customers leveraging ISV data for connector-heavy Okta Identity Governance (OIG) use cases.<\/p>\n\n<h2 id=\"benefits-of-low-code-api-integration-for-isvs\">Benefits of low-code API integration for ISVs<\/h2>\n\n<p>For the ISV application developer:<\/p>\n\n<ul>\n  <li>Built on Workflows: use the low-code builder instead of writing and maintaining complex code<\/li>\n  <li>Translates your API calls into formats consumable by Okta: bring your APIs as they are, without having to make any changes<\/li>\n  <li>No need for in-depth knowledge of protocols: Workflows makes mapping your API to Okta\u2019s format simple<\/li>\n  <li>No need to invest in costly infrastructure: don\u2019t worry about managing a SCIM server<\/li>\n  <li>It\u2019s not just secure \u2014 it\u2019s fast and easy!<\/li>\n<\/ul>\n\n<h2 id=\"how-to-build-low-code-api-integrations-with-okta-workflows\">How to build low-code API integrations with Okta Workflows<\/h2>\n\n<p>If you don\u2019t already have an account, sign up for an <a href=\"https:\/\/developer.okta.com\/signup\/\">Okta Integrator Free Plan<\/a> first. Once created, log in and follow these steps.<\/p>\n\n<h3 id=\"step-1-create-your-oin-integration\">Step 1: Create your OIN integration<\/h3>\n\n<ul>\n  <li>Click <strong>Applications<\/strong> &gt; <strong>Your OIN Integrations<\/strong><\/li>\n  <li>Click <strong>Build new OIN integration<\/strong><\/li>\n  <li>Choose the single sign-on (SSO) type<\/li>\n  <li>If you are building an integration that uses Universal Logout, choose that option. If you are building an integration using provisioning and entitlements, choose those options<\/li>\n  <li>Select <strong>View integration details<\/strong><\/li>\n<\/ul>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/add-integration-capabilities-de356175390fd0bb655700f24f6d620de643de30b5ee19a123b0c72035d4d184.jpg\" alt=\"Add integration capabilities screen showing Session Lifecycle Management and Identity Lifecycle Management options\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<ul>\n  <li>Add the integration details<\/li>\n  <li>If you are a customer creating an integration for your orgs during the EA period, put \u201cCustomer-created integration - not for the public catalog\u201d in the description field. Then provide a list of your org tenant IDs and subdomains. After submission, you will need to email your account manager to ensure this integration is deployed to your orgs.<\/li>\n<\/ul>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/catalog-props-1-42da508ba461b07218328e6a95a110860eaaf2c537fb85c1cf021a5f0b70d552.jpg\" alt=\"OIN catalog properties form showing display name, description, and logo upload fields\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/catalog-prop-2-cefa62a0ba05fbcd6a882897a2c8b43d90a81c560aa7fffb1e7d0cd1e993b5f7.jpg\" alt=\"OIN catalog properties form continued showing support contact information and use case options\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<h3 id=\"step-2-configure-authentication-and-api-integration-actions\">Step 2: Configure authentication and API Integration Actions<\/h3>\n\n<ul>\n  <li>Tenant settings refer to subdomains or additional information needed for the SSO components<\/li>\n  <li>Authentication settings include all of the allowed integration types. Choose the one used by the API and provide the information<\/li>\n  <li>Click <strong>Save and start building<\/strong><\/li>\n<\/ul>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/tenant-auth-3f0d2a1419a2b4ef969758b56f75c918d1bd420be3c811b49eb4ae92c98c017f.jpg\" alt=\"Tenant settings and authentication settings screens showing label, name fields, and OAuth 2 configuration with authorize and token endpoints\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<ul>\n  <li>This will send you to <strong>Integration Builder<\/strong> within the Okta Workflows product, where you build out the flows that connect to the API<\/li>\n  <li>Validate that the information is correct \u2014 it should match what was provided in OIN Wizard<\/li>\n<\/ul>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/wf-project-7ac612cdc7ceaa860dfb568367bfb216e1de464b396c32d4c952e7ac849c0c67.jpg\" alt=\"Integration Builder project screen showing General, Authentication, Test connection, and API Spec tabs\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<ul>\n  <li>Click on the <strong>Authentication<\/strong> tab and add the authentication information. Make sure it matches what is in the OIN Wizard<\/li>\n  <li>Fill out the <strong>Authentication Mapping<\/strong> section to map the OIN Wizard auth parameters to the Workflows auth parameters<\/li>\n<\/ul>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/auth-mapping-4abb7154894f018550d250e7db054f05ba6f4838ae2878de761dc4a31fc8866d.jpg\" alt=\"Authentication mapping screen showing connection parameters mapped to OIN app integration variables\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<ul>\n  <li>Click on <strong>New Component<\/strong> and choose <strong>Add Action<\/strong><\/li>\n  <li>Choose the API Integration Action component from the list, and click <strong>save<\/strong><\/li>\n<\/ul>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/choose-action-d397cabbed4ab58e70c8c4602d677211c989957b6fc984d83cb1d3a467281676.jpg\" alt=\"Add new action dialog showing API integration action component options, including Provisioning action contracts\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<h3 id=\"step-3-build-your-low-code-workflow-flows\">Step 3: Build your low-code workflow flows<\/h3>\n\n<ul>\n  <li>Click on <strong>New Flow<\/strong><\/li>\n  <li>Create the workflow and repeat as necessary<\/li>\n  <li>Once your flows are created, you can create test flows in the test folder to validate that the API calls are being made correctly<\/li>\n<\/ul>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/flow-list-982e6ea3cea32eefc6d8acc0d4aadc097b6127311476704d4155c4a835bdff6d.jpg\" alt=\"Provisioning action contracts screen showing App Event flows for List users, Get group by id, List groups, and more\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<ul>\n  <li>After testing, click on <strong>Validate and Submit<\/strong><\/li>\n  <li>Click on <strong>Validate flows<\/strong> and fix any errors that may exist<\/li>\n<\/ul>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/validate-flows-9577e21a89b466cc9a6d70bdd13f924af10ec0f170ed58a3f3a1249a4d99819e.jpg\" alt=\"Validate and submit flows screen showing flow validation status and Continue submission in OIN button\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<ul>\n  <li>Click on <strong>Continue submission in OIN<\/strong><\/li>\n  <li>Back in the OIN Wizard, choose the correct flows for each of the API Integration Actions that have been created<\/li>\n  <li>Click on <strong>Get started with testing<\/strong><\/li>\n<\/ul>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/configure-integration-d4c0e694213b005d4d8798ff96ed30ed6a886b738af0419dd39cac988c4f86e7.jpg\" alt=\"Provisioning API Integration Actions screen showing User query, User Schema Discovery, and User Operations flow mapping\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<h2 id=\"how-to-test-your-api-integration-before-publishing-to-the-oin\">How to test your API integration before publishing to the OIN<\/h2>\n\n<p>Before submitting your integration for review and publication, you must test it in your Okta org. Your integration will only be available on your Okta org. Okta admins will see the same authorization experience.<\/p>\n\n<ul>\n  <li>Provide the testing information needed for Okta to review the submission<\/li>\n  <li>Once finished, click on <strong>Test your integration<\/strong><\/li>\n<\/ul>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/test-instance-da421c1d820fd16dfe7261b7c836a97665d5e5d37ef1dafd05a219d2794c2bc3.jpg\" alt=\"Test your integration screen showing test account fields, account URL, username, password, and testing instructions\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<h3 id=\"create-a-test-instance\">Create a test instance<\/h3>\n\n<ul>\n  <li>Fill out the information, including the test account and any SSO testing features on the <strong>Test your integration<\/strong> section of the OIN Wizard<\/li>\n  <li>Click <strong>Test your integration<\/strong><\/li>\n  <li>Follow the instructions in the <strong>Test integration<\/strong> section to generate a test instance and complete all of the testing<\/li>\n  <li>Validate your flows by clicking the button \u2014 take action on any failures that occur<\/li>\n<\/ul>\n\n<p><img src=\"\/assets-jekyll\/blog\/low-code-api-integration\/test-integration-2273cb1e49820cf0c061b6806c10e1f95bd7d7de80b3f0d1eefacd01f79ed15a.jpg\" alt=\"Test integration screen showing app instances for testing with SAML SSO instance detected and Provisioning and Entitlement instances pending\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<h3 id=\"update-a-test-instance\">Update a test instance<\/h3>\n\n<p>When you make an update to your submission in the OIN Manager (for example, modifying the scopes or name of the integration), the update will not automatically be reflected in your test instance for security reasons.<\/p>\n\n<p>To update a test instance, repeat the procedure above for creating a test instance.<\/p>\n\n<p>Once finished, click the last checkbox to enable submitting your integration for review. This process is similar to the existing OIN Catalog process.<\/p>\n\n<h2 id=\"get-started-with-low-code-api-integration-using-okta\">Get started with low-code API integration using Okta<\/h2>\n\n<p>If you\u2019re ready to build an integration between your APIs and Okta\u2019s, start by exploring how to build and publish an application using API Integration Actions to the OIN by reading our product documentation <a href=\"\/docs\/guides\/build-api-actions\/main\/\">Build and publish API Integration Actions<\/a>.<\/p>\n\n<p>Remember to follow us on <a href=\"https:\/\/twitter.com\/oktadev\">Twitter<\/a> and subscribe to our <a href=\"https:\/\/www.youtube.com\/c\/OktaDev\/\">YouTube channel<\/a> for more exciting content. We also want to hear from you about the topics you\u2019d like to see and any questions you may have. Leave us a comment below!<\/p>\n","pubDate":"Tue, 12 May 2026 00:00:00 -0500","link":"https:\/\/developer.okta.com\/blog\/2026\/05\/12\/low-code-api-integration","guid":"https:\/\/developer.okta.com\/blog\/2026\/05\/12\/low-code-api-integration"},{"title":"Develop a XAA-Enabled Resource Application and Test with Okta","description":"<p>From an enterprise resource app owner\u2019s perspective, Cross App Access (XAA) is a game-changer because it allows their resources to be \u201cAI-ready\u201d without compromising on security. In the XAA model, resource apps rely on the enterprise\u2019s Identity Provider (IdP) to manage access. Instead of building out interactive OAuth flows, they defer to the IdP to check enterprise policies and user groups, assign AI agent permissions, and log and audit AI agent requests as they occur. In return, the app\u2019s OAuth server needs only to perform a few checks:<\/p>\n\n<ul>\n  <li>When the app\u2019s OAuth server receives a POST request to its token endpoint from an AI agent, the app fetches the IdP\u2019s public keys (via the JWKS endpoint) to ensure the ID-JAG token attached to the request was actually minted by the trusted company IdP.<\/li>\n  <li>It confirms the token was intended for this app specifically. If the <code class=\"language-plaintext highlighter-rouge\">aud<\/code> claim doesn\u2019t match the app\u2019s own identifier, it rejects the request.<\/li>\n  <li>Finally, it checks the end user ID in the token\u2019s <code class=\"language-plaintext highlighter-rouge\">sub<\/code> claim to know whose data to look up in your database. It must map to the same IdP identity. It will reject the request if the user isn\u2019t recognized.<\/li>\n<\/ul>\n\n<p>You can read in depth about XAA to better understand how this works and examine the token exchange flow.<\/p>\n\n<article class=\"link-container\" style=\"border: 1px solid silver; border-radius: 3px; padding: 12px 15px\">\n              <a href=\"\/blog\/2025\/06\/23\/enterprise-ai\" style=\"font-size: 1.375em; margin-bottom: 20px;\">\n                <span>Integrate Your Enterprise AI Tools with Cross-App Access<\/span>\n              <\/a>\n              <p>Manage user and non-human identities, including AI in the enterprise with Cross App Access<\/p>\n              <div><div class=\"BlogPost-attribution\">\n            <a href=\"\/blog\/authors\/semona-igama\/\">\n              <img src=\"\/assets-jekyll\/avatar-semona-igama-03eb4c28aca3765f862b574e032d32f6f8186d04ae9f0db75bed9c74f48a9a3f.jpg\" alt=\"avatar-avatar-semona-igama.jpeg\" class=\"BlogPost-avatar\" \/>\n            <\/a>\n            <span class=\"BlogPost-author\">\n                <a href=\"\/blog\/authors\/semona-igama\/\">Semona Igama<\/a>\n            <\/span>\n          <\/div><\/div>\n          <\/article>\n\n<p>Or watch the video about Cross App Access:<\/p>\n\n<div class=\"jekyll-youtube-plugin\" style=\"text-align: center; margin-bottom: 1.25rem\">\n            <iframe width=\"700\" height=\"394\" style=\"max-width: 100%\" src=\"https:\/\/www.youtube.com\/embed\/3VLzeT1EGrg\" allowfullscreen=\"\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" frameborder=\"0\"><\/iframe>\n        <\/div>\n\n<p>In this tutorial, we\u2019ll demonstrate how to test that an XAA-enabled resource app you have created (<strong>TaskFlow<\/strong>) is correctly using Okta as an <strong>enterprise Identity Provider (IdP)<\/strong> to sign users in, and we\u2019ll demonstrate how a sample AI app (<strong>Agent0<\/strong>) uses XAA to get access to TaskFlow. To do this, you\u2019ll:<\/p>\n\n<ul>\n  <li>Enable Cross App Access in your Okta org<\/li>\n  <li>Register and configure the resource app (TaskFlow) in your org<\/li>\n  <li>Register the requesting app (Agent0) in your org as a known XAA app and connect it to TaskFlow.<\/li>\n  <li>Test that the XAA flow is working correctly when Agent0 requests access to TaskFlow.<\/li>\n<\/ul>\n\n<blockquote>\n  <p>Note that the apps (TaskFlow or Agent0) do not use Okta as their authorization server.<\/p>\n<\/blockquote>\n\n<h1 id=\"enable-cross-app-access-in-your-okta-org\">Enable Cross App Access in your Okta org<\/h1>\n\n<p>To register your resource app with Okta, and set up secure agent-to-app connections, you\u2019ll need an Okta Developer org enabled with XAA:<\/p>\n\n<ul>\n  <li>If you don\u2019t already have an account, sign up for a new one here: <a href=\"https:\/\/developer.okta.com\/signup\">Okta Integrator Free Plan<\/a><\/li>\n  <li>Once created, sign in to your new Integrator Free Plan org<\/li>\n  <li>In the Okta Admin Console, select <strong>Settings &gt; Features<\/strong><\/li>\n  <li>Navigate to <strong>Early access<\/strong><\/li>\n  <li>Find <strong>Cross App Access<\/strong> and select <strong>Turn on<\/strong> (enable the toggle)<\/li>\n  <li>Refresh the Admin Console<\/li>\n<\/ul>\n\n<blockquote>\n  <p>Note: Cross App Access is currently a self-service Early Access (EA) feature. You must enable it through the Admin Console before the apps appear in the catalog. If you don\u2019t see the option right away, refresh and confirm you have the necessary admin permissions. Learn more in the <a href=\"https:\/\/help.okta.com\/oie\/en-us\/content\/topics\/security\/manage-ea-and-beta-features.htm\">Okta documentation on managing EA and beta features<\/a>.<\/p>\n<\/blockquote>\n\n<p><img src=\"\/assets-jekyll\/blog\/xaa-resource-app\/image3-c9a95bb9918d5d631678622b2343d7776e314875e72ecd70fea836d264d8164c.jpg\" alt=\" \" width=\"800\" class=\"center-image\" \/><\/p>\n\n<h1 id=\"register-your-requesting-app-agent0\">Register your requesting app (Agent0)<\/h1>\n\n<p>To test whether your resource app is working correctly, Okta provides a placeholder entry in the Okta Integration Network catalog. It is called <strong><em>Agent0 - Cross App Access (XAA) Sample Requesting App<\/em><\/strong>. Add this to your org\u2019s integrations.<\/p>\n\n<ul>\n  <li>Still in Admin Console, go to <strong>Applications &gt; Applications<\/strong><\/li>\n  <li>Select <strong>Browse App Catalog<\/strong><\/li>\n  <li>Search for \u201cAgent0 - Cross App Access (XAA) Sample Requesting App\u201d, and select it<\/li>\n  <li>Select <strong>Add Integration<\/strong><\/li>\n<\/ul>\n\n<p>Now to configure it correctly. First, assign user access to Agent0.<\/p>\n\n<ul>\n  <li>Change the <strong>Application<\/strong> label if required, and select <strong>Done<\/strong>,<\/li>\n  <li>Select the Assignments tab\n    <ul>\n      <li>To assign it to a single user, select <strong>Assign &gt; Assign to People<\/strong> and choose your user<\/li>\n      <li>To assign it to a user group, select <strong>Assign &gt; Assign to Groups<\/strong> and choose your user group<\/li>\n    <\/ul>\n  <\/li>\n  <li>Click Done<\/li>\n<\/ul>\n\n<p>Finally, configure Agent0 with the redirect URI you will use to test Agent0<\/p>\n\n<ul>\n  <li>Select the <strong>Sign On tab<\/strong><\/li>\n  <li>Select <strong>Edit<\/strong>, and locate the Advanced Sign-on Settings section.<\/li>\n  <li>Set the <strong>Redirect URI<\/strong> to the URL that your app will use. For example, <a href=\"http:\/\/localhost:8080\/redirect\">http:\/\/localhost:8080\/redirect<\/a><\/li>\n  <li>Click Save.<\/li>\n  <li>Locate and copy the Client ID and Client secret in the Sign-On methods section. Your app must use these when signing users in through Okta.<\/li>\n<\/ul>\n\n<blockquote>\n  <p>Note: Only the org authorization server can be used to exchange ID-JAG tokens. Ensure you are using the org authorization server and not an Okta \u201ccustom authorization server\u201d.<\/p>\n<\/blockquote>\n\n<p><img src=\"\/assets-jekyll\/blog\/xaa-resource-app\/image2-ad5eed8a0e6b495caa26809fb24390efdd64204ac8c638c84d249a028882537b.jpg\" alt=\" \" width=\"800\" class=\"center-image\" \/><\/p>\n\n<h2 id=\"get-a-xaa-client-id-for-agent0-from-the-resource-apps-auth-server\">Get a (XAA) Client ID for Agent0 from the Resource app\u2019s Auth Server<\/h2>\n\n<p>To allow the exchange of an ID-JAG token between Agent0 and your resource app, Agent0 must be registered as an OAuth client in your resource app\u2019s OAuth server.<\/p>\n\n<ul>\n  <li>Register your requesting app (<strong>Agent0<\/strong>) as an OAuth client in your resource app\u2019s OAuth server.<\/li>\n  <li>Make a note of the Client ID for your requesting app (<strong>Agent0<\/strong>). You\u2019ll need this as you set up your resource app.<\/li>\n<\/ul>\n\n<blockquote>\n  <p>Note: The process for registering a client ID from your resource app\u2019s OAuth server will vary depending on the product.<\/p>\n<\/blockquote>\n\n<h1 id=\"set-up-your-resource-app-taskflow\">Set up your resource app (TaskFlow)<\/h1>\n\n<p>To set up your resource app in your org, you can use the placeholder integration in the OIN catalog called <strong><em>Todo0 - Cross App Access (XAA) Sample Resource App<\/em><\/strong> and configure it as your resource app.<\/p>\n\n<ul>\n  <li>Still in Admin Console, navigate to <strong>Applications &gt; Applications<\/strong><\/li>\n  <li>Select <strong>Browse App Catalog<\/strong><\/li>\n  <li>Search for <strong>Todo0 - Cross App Access (XAA) Sample Resource App<\/strong>, and select it<\/li>\n  <li>Select <strong>Add Integration<\/strong><\/li>\n<\/ul>\n\n<p>Now give it a helpful name and assign user access to TaskFlow.<\/p>\n\n<ul>\n  <li>Set the Application label to <strong><em>TaskFlow<\/em><\/strong>, and click Done.<\/li>\n  <li>Select the <strong>Assignments<\/strong> tab\n    <ul>\n      <li>To assign it to a single user, select <strong>Assign &gt; Assign to People<\/strong> and choose your user<\/li>\n      <li>To assign it to a user group, select <strong>Assign &gt; Assign to Groups<\/strong> and choose your user group<\/li>\n    <\/ul>\n  <\/li>\n  <li>Click <strong>Done<\/strong><\/li>\n<\/ul>\n\n<h2 id=\"update-the-audience-value-of-your-resource-apps-auth-server\">Update the audience value of your Resource app\u2019s auth server<\/h2>\n\n<p>By default, Okta will issue an ID-JAG token for Agent0 with the audience (<code class=\"language-plaintext highlighter-rouge\">aud<\/code>) value set to that of the sample resource app (Todo0): <code class=\"language-plaintext highlighter-rouge\">http:\/\/localhost:5001\/<\/code>. You must change this so the ID-JAG token includes an audience value that identifies your actual resource app\u2019s authorization server.<\/p>\n\n<p>To do this, contact the Okta XAA team to replace your app\u2019s audience value in Okta by sending an email to xaa@okta.com. Provide the following information to the Okta XAA team:<\/p>\n\n<p><strong><em>Okta Integrator Org URL:<\/em><\/strong> \u2018\u2019<br \/>\n<strong><em>Audience:<\/em><\/strong> \u2018http:\/\/yourresourceapps.authserver.org\u2019\n<strong><em>Client ID from your own OAuth server:<\/em><\/strong> [Agent0\u2019s XAA client ID you created earlier]<\/p>\n\n<p>Please note that the Client ID you provide must be the client ID from your own OAuth server that was created earlier.<\/p>\n\n<h1 id=\"establish-connections-between-agent0-and-your-resource-app\">Establish Connections between Agent0 and your resource app<\/h1>\n\n<p>Now that you have set up both requesting and resource apps, you need to establish that Agent0 can be trusted to make requests to your resource app.<\/p>\n\n<ul>\n  <li>Still in Admin Console, navigate to <strong>Applications &gt; Applications &gt; Agent0<\/strong><\/li>\n  <li>Go to the <strong>Manage Connections<\/strong> tab<\/li>\n  <li>Under <strong>Apps providing consent<\/strong>, select <strong>Add resource apps<\/strong>, select <strong>TaskFlow<\/strong>, then <strong>Save<\/strong><\/li>\n  <li>Confirm that your resource app appears under <strong>Apps providing consent<\/strong><\/li>\n<\/ul>\n\n<p>Now Agent0 and TaskFlow are connected.<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/xaa-resource-app\/image1-723faeb6e83b3230d953dadecfc1e00b0483aa4db06c25d6c1ca30a265f504ae.jpg\" alt=\" \" width=\"800\" class=\"center-image\" \/><\/p>\n\n<h1 id=\"validate-that-your-resource-app-and-auth-server-work-as-intended\">Validate that your Resource App and Auth Server work as intended<\/h1>\n\n<p>Once the Okta XAA team confirms that your app\u2019s audience value has been updated in Okta, Agent0 can make a Token Exchange request to Okta and will receive an ID-JAG with the correct audience.<\/p>\n\n<p>To test the end-to-end XAA flow with Agent0 to your authorization server, create a testing client that completes the following steps:<\/p>\n\n<ol>\n  <li>Agent0 signs the user in with OIDC.<\/li>\n  <li>Agent0 exchanges the ID token for an ID-JAG at Okta<\/li>\n  <li>Agent0 makes a token request with the ID-JAG at your authorization server<\/li>\n<\/ol>\n\n<p>If you need support with taking the steps above, contact xaa@okta.com.<\/p>\n\n<p>With testing complete, consider publicizing your resource app on the Okta Integration Network (OIN) catalog. Adding it to the catalog makes it easy for Okta\u2019s roughly 18000 enterprise customers to learn about and add it to the suite of tools on their Okta dashboards.<\/p>\n\n<h1 id=\"learn-more-about-cross-app-access-oauth-20-and-securing-your-applications\">Learn more about Cross App Access, OAuth 2.0, and securing your applications<\/h1>\n\n<p>If this walkthrough helped you understand more about how Cross App Access works in practice, consider learning more about<\/p>\n\n<p>\ud83d\udcd8 <a href=\"https:\/\/xaa.dev\/\">xaa.dev<\/a> - a free, open sandbox that lets you explore Cross App Access end-to-end. No local setup. No infrastructure to provision. Just a working environment where you can see the protocol in action.<br \/>\n\ud83d\udcd8 <a href=\"https:\/\/help.okta.com\/oie\/en-us\/content\/topics\/apps\/apps-cross-app-access.htm\">Okta\u2019s Cross App Access Documentation<\/a> \u2013 official guides and admin docs to configure and manage Cross App Access in production<br \/>\n\ud83c\udf99\ufe0f <a href=\"https:\/\/www.youtube.com\/watch?v=qKs4k5Y1x_s\">Okta Developer Podcast on MCP and Cross App Access<\/a> \u2013 hear the backstory, use cases, and why this matters for developers<br \/>\n\ud83d\udcc4 <a href=\"https:\/\/datatracker.ietf.org\/doc\/draft-ietf-oauth-identity-assertion-authz-grant\/\">OAuth Identity Assertion Authorization Grant (IETF Draft)<\/a> \u2013 the emerging standard that powers this flow<\/p>\n\n","pubDate":"Tue, 17 Feb 2026 00:00:00 -0500","link":"https:\/\/developer.okta.com\/blog\/2026\/02\/17\/xaa-resource-app","guid":"https:\/\/developer.okta.com\/blog\/2026\/02\/17\/xaa-resource-app"},{"title":"Make Secure App-to-App Connections Using Cross App Access","description":"<p>Imagine you built a note-taking app. It\u2019s so successful that LargeCorp, an aptly named large enterprise corporation, signed on as a customer. To make it a power tool for your enterprise customers, you need to allow your app to integrate with other productivity tools, such as turning a note into a task in a to-do app.<\/p>\n\n<p>While common integration patterns work well for individual users, these patterns create security and compliance hurdles for large organizations.<\/p>\n\n<h2 id=\"limitations-of-api-keys-and-oauth-in-enterprise-app-to-app-connectivity\">Limitations of API keys and OAuth in enterprise app-to-app connectivity<\/h2>\n\n<p>Connecting independent apps usually involves one of two common strategies. Both have significant drawbacks when used in a corporate environment:<\/p>\n<ul>\n  <li><strong>API keys and service accounts<\/strong>\n  These lack user context. They often lead to over-privileged access and create challenging rotation requirements.<\/li>\n  <li><strong>Standard OAuth 2.0<\/strong>\n  A much better, industry-standard best practice over API keys and service accounts, but this relies on individual user consent. IT admins cannot see or control which apps employees connect to, creating shadow IT risks and compliance and security concerns.<\/li>\n<\/ul>\n\n<h2 id=\"cross-app-access-xaa-extends-oauth-flows-to-manage-application-access\">Cross App Access (XAA) extends OAuth flows to manage application access<\/h2>\n\n<p>Cross App Access is an OAuth extension based on the <a href=\"https:\/\/drafts.oauth.net\/oauth-identity-assertion-authz-grant\/draft-ietf-oauth-identity-assertion-authz-grant.html\">Identity Assertion Authorization Grant<\/a>. It addresses these challenges by using the Enterprise Identity Provider (IdP) as a central broker and was proposed by a collaborative group of organizations and interested individuals.<\/p>\n\n<p>With XAA, the Identity Provider (IdP) facilitates a secure token exchange. This provides three main benefits.<\/p>\n<ul>\n  <li>IT Governance - Admins centrally manage and approve app-to-app connections<\/li>\n  <li>Reduced friction - Users avoid repeated and confusing consent prompts<\/li>\n  <li>Granular security - Access is limited to specific users and specific tasks.<\/li>\n<\/ul>\n\n<p>You can read in depth about XAA in <a href=\"\/blog\/2025\/06\/23\/enterprise-ai\">Integrate Your Enterprise AI Tools with Cross App Access<\/a> to better understand how this works and to look at the token exchange flow<\/p>\n\n<article class=\"link-container\" style=\"border: 1px solid silver; border-radius: 3px; padding: 12px 15px\">\n              <a href=\"\/blog\/2025\/06\/23\/enterprise-ai\" style=\"font-size: 1.375em; margin-bottom: 20px;\">\n                <span>Integrate Your Enterprise AI Tools with Cross-App Access<\/span>\n              <\/a>\n              <p>Manage user and non-human identities, including AI in the enterprise with Cross App Access<\/p>\n              <div><div class=\"BlogPost-attribution\">\n            <a href=\"\/blog\/authors\/semona-igama\/\">\n              <img src=\"\/assets-jekyll\/avatar-semona-igama-03eb4c28aca3765f862b574e032d32f6f8186d04ae9f0db75bed9c74f48a9a3f.jpg\" alt=\"avatar-avatar-semona-igama.jpeg\" class=\"BlogPost-avatar\" \/>\n            <\/a>\n            <span class=\"BlogPost-author\">\n                <a href=\"\/blog\/authors\/semona-igama\/\">Semona Igama<\/a>\n            <\/span>\n          <\/div><\/div>\n          <\/article>\n\n<p>In this tutorial, we\u2019ll add XAA to connect a note-taking app to a to-do app using <a href=\"https:\/\/xaa.dev\">xaa.dev<\/a> as our testing ground.<\/p>\n\n<p><strong class=\"hide\">Table of Contents<\/strong><\/p>\n<ul id=\"markdown-toc\">\n  <li><a href=\"#limitations-of-api-keys-and-oauth-in-enterprise-app-to-app-connectivity\" id=\"markdown-toc-limitations-of-api-keys-and-oauth-in-enterprise-app-to-app-connectivity\">Limitations of API keys and OAuth in enterprise app-to-app connectivity<\/a><\/li>\n  <li><a href=\"#cross-app-access-xaa-extends-oauth-flows-to-manage-application-access\" id=\"markdown-toc-cross-app-access-xaa-extends-oauth-flows-to-manage-application-access\">Cross App Access (XAA) extends OAuth flows to manage application access<\/a><\/li>\n  <li><a href=\"#make-app-to-app-requests-using-cross-app-access\" id=\"markdown-toc-make-app-to-app-requests-using-cross-app-access\">Make app-to-app requests using Cross App Access<\/a><\/li>\n  <li><a href=\"#bring-your-own-requestor-app-to-the-xaadev-testing-site\" id=\"markdown-toc-bring-your-own-requestor-app-to-the-xaadev-testing-site\">Bring your own requestor app to the xaa.dev testing site<\/a><\/li>\n  <li><a href=\"#get-the-nestjs-project-with-oauth-and-openid-connect-oidc-started\" id=\"markdown-toc-get-the-nestjs-project-with-oauth-and-openid-connect-oidc-started\">Get the NestJS project with OAuth and OpenID Connect (OIDC) started<\/a><\/li>\n  <li><a href=\"#exchanging-an-id-token-for-an-access-token-for-another-app\" id=\"markdown-toc-exchanging-an-id-token-for-an-access-token-for-another-app\">Exchanging an ID token for an access token for another app<\/a>    <ul>\n      <li><a href=\"#exchange-the-id-token-for-an-intermediary-id-jag-token-type\" id=\"markdown-toc-exchange-the-id-token-for-an-intermediary-id-jag-token-type\">Exchange the ID token for an intermediary ID-JAG token type<\/a><\/li>\n      <li><a href=\"#use-the-id-jag-token-to-request-an-access-token-for-a-separate-app\" id=\"markdown-toc-use-the-id-jag-token-to-request-an-access-token-for-a-separate-app\">Use the ID-JAG token to request an access token for a separate app<\/a><\/li>\n    <\/ul>\n  <\/li>\n  <li><a href=\"#inspecting-the-xaa-token-exchange\" id=\"markdown-toc-inspecting-the-xaa-token-exchange\">Inspecting the XAA token exchange<\/a><\/li>\n  <li><a href=\"#learn-more-about-xaa-and-elevating-identity-security-using-oauth\" id=\"markdown-toc-learn-more-about-xaa-and-elevating-identity-security-using-oauth\">Learn more about XAA and elevating identity security using OAuth<\/a><\/li>\n<\/ul>\n\n<h2 id=\"make-app-to-app-requests-using-cross-app-access\">Make app-to-app requests using Cross App Access<\/h2>\n\n<p>We\u2019re using <a href=\"https:\/\/nestjs.com\/\">NestJS<\/a> in this project. The tech stack relies on TypeScript, and we\u2019ll use an OpenID Connect (OIDC) client library to communicate with the IdP and the to-do app\u2019s OAuth Authorization server. Using a well-maintained OIDC client library is a best practice when creating apps that use OAuth flows, as it helps ensure you don\u2019t make subtle errors in OAuth handshakes that compromise security.<\/p>\n\n<p>For this workshop, you need the following required tooling:<\/p>\n\n<p><strong>Required tools<\/strong><\/p>\n<ul>\n  <li><a href=\"https:\/\/nodejs.org\/en\">Node.js<\/a> LTS version (v22 or higher at the time of this post)<\/li>\n  <li>Command-line terminal application<\/li>\n  <li>A code editor\/Integrated development environment (IDE), such as <a href=\"https:\/\/code.visualstudio.com\/\">Visual Studio Code<\/a> (VS Code)<\/li>\n  <li><a href=\"https:\/\/git-scm.com\/\">Git<\/a><\/li>\n<\/ul>\n\n<blockquote>\n  <p><strong>Note<\/strong><\/p>\n\n  <p>This code project is best for developers with web development and TypeScript experience and familiarity with OAuth and OpenID Connect (OIDC) flows at a high level.<\/p>\n<\/blockquote>\n\n<p>If you want to skip directly to the working project, you can find it <a href=\"https:\/\/github.com\/oktadev\/okta-js-xaa-requestor-example\">in the GitHub repo<\/a>.<\/p>\n\n<h2 id=\"bring-your-own-requestor-app-to-the-xaadev-testing-site\">Bring your own requestor app to the xaa.dev testing site<\/h2>\n\n<p>The <a href=\"https:\/\/xaa.dev\">xaa.dev<\/a> testing site supports testing local client apps. It\u2019s IdP-agnostic, meaning it\u2019s focused on the spec and education, not on a specific company\u2019s product line. In this scenario, we can verify whether our client app, the note-taking app, handles the token exchange with an IdP and the resource app\u2019s authorization server. The best part about this testing site is that it\u2019s self-contained and works out of the box. So you don\u2019t need to create an account with an IdP, nor do you have a resource app with a conformant OAuth authorization server! We just have to bring our client code for testing! Yay for simplicity!<\/p>\n\n<p>You can read more about the site here:<\/p>\n\n<article class=\"link-container\" style=\"border: 1px solid silver; border-radius: 3px; padding: 12px 15px\">\n              <a href=\"\/blog\/2026\/01\/20\/xaa-dev-playground\" style=\"font-size: 1.375em; margin-bottom: 20px;\">\n                <span>Introducing xaa.dev: A Playground for Cross App Access<\/span>\n              <\/a>\n              <p>Explore Cross App Access end-to-end with xaa.dev, a free, open playground that lets you test the XAA protocol without any local setup or infrastructure.<\/p>\n              <div><div class=\"BlogPost-attribution\">\n            <a href=\"\/blog\/authors\/sohail-pathan\/\">\n              <img src=\"\/assets-jekyll\/avatar-sohail-pathan-fa148e78133752dcc86034268bffe3367e2708874b1ea957b09712e8937b8cc7.jpg\" alt=\"avatar-avatar-sohail-pathan.jpeg\" class=\"BlogPost-avatar\" \/>\n            <\/a>\n            <span class=\"BlogPost-author\">\n                <a href=\"\/blog\/authors\/sohail-pathan\/\">Sohail Pathan<\/a>\n            <\/span>\n          <\/div><\/div>\n          <\/article>\n\n<p>Let\u2019s register our note-taking app now.<\/p>\n\n<p>In your browser, navigate to <a href=\"https:\/\/xaa.dev\">xaa.dev<\/a>. The main site provides information about the players in this flow, and you can test the XAA flow step by step there. Please take a moment to step through the flow to get a better sense of the code we\u2019ll build.<\/p>\n\n<p>When you\u2019re ready, navigate to <strong>Developer<\/strong> &gt; <strong>Register Client<\/strong>. Add a totally made-up email for more fun when registering.<\/p>\n\n<p>Select <strong>+ Register New Client<\/strong> and fill out the required information:<\/p>\n<ul>\n  <li><strong>Application Name<\/strong> - I used \u201cNotes App\u201d<\/li>\n  <li><strong>Redirect URIs<\/strong> - Enter <code class=\"language-plaintext highlighter-rouge\">http:\/\/localhost:3000\/auth\/callback<\/code><\/li>\n  <li><strong>Post-Logout Redirect URIs<\/strong> - Enter <code class=\"language-plaintext highlighter-rouge\">http:\/\/localhost:3000<\/code><\/li>\n  <li><strong>Resource Connections &gt; Add Resource<\/strong> - Choose \u201cTodo0 Resource App\u201d and mark \u201ctodos.read\u201d as your allowed scopes before clicking the Add Connection button.<\/li>\n<\/ul>\n\n<p>Once all necessary fields have been filled select <strong>Register App<\/strong>.<\/p>\n\n<p>You\u2019ll see a modal with the Client ID and Client Secret. The xaa.dev testing site also provides credentials for the resource app\u2019s authorization server - the Resource Client ID and Resource Client Secret. Copy all four values. We need to add these to our project.<\/p>\n\n<h2 id=\"get-the-nestjs-project-with-oauth-and-openid-connect-oidc-started\">Get the NestJS project with OAuth and OpenID Connect (OIDC) started<\/h2>\n\n<p>You\u2019ll use a starter note-taking app project written in NestJS. Before you get too excited, remember this is a demo app. While the note-taking features are minimal, it does include built-in authentication.<\/p>\n\n<p>Open a terminal window and run the following commands to get a local copy of the project in a directory named <code class=\"language-plaintext highlighter-rouge\">okta-xaa-project<\/code> and install dependencies. Feel free to fork the repo to track your changes.<\/p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>git clone <span class=\"nt\">-b<\/span> starter https:\/\/github.com\/oktadev\/okta-js-xaa-requestor-example.git okta-xaa-project\n<span class=\"nb\">cd <\/span>okta-xaa-project\nnpm ci\n<\/code><\/pre><\/div><\/div>\n\n<p>Open the project in your IDE. Let\u2019s go over the main components and framework choices so you don\u2019t have to discover everything on your own:<\/p>\n<ol>\n  <li>The NestJS project depends on <a href=\"https:\/\/expressjs.com\/\">Express<\/a> as the base engine and uses <a href=\"https:\/\/www.typescriptlang.org\/\">TypeScript<\/a>.<\/li>\n  <li>Views for the landing page and the notes interface use <a href=\"https:\/\/mozilla.github.io\/nunjucks\/\">Nunjucks<\/a> as the templating engine.<\/li>\n  <li>Relies on the <a href=\"https:\/\/github.com\/panva\/openid-client\/tree\/main\">openid-client<\/a> to handle all OAuth handshakes. It\u2019s an OIDC client library for JavaScript runtimes.<\/li>\n  <li>There\u2019s a basic interceptor implementation that logs HTTP requests and responses to the console. This way, we can see the token exchange flow.<\/li>\n<\/ol>\n\n<p>The app requires a client ID, client secret, resource client ID, and resource client secret to run. Let\u2019s add those to the project.<\/p>\n\n<p>Rename the <code class=\"language-plaintext highlighter-rouge\">.env.example<\/code> file to <code class=\"language-plaintext highlighter-rouge\">.env<\/code>. It already has variables defined and values added to match the URI of the XAA testing site components. Replace the <code class=\"language-plaintext highlighter-rouge\">CLIENT_ID<\/code>, <code class=\"language-plaintext highlighter-rouge\">CLIENT_SECRET<\/code>, <code class=\"language-plaintext highlighter-rouge\">RESOURCE_CLIENT_ID<\/code>, and <code class=\"language-plaintext highlighter-rouge\">RESOURCE_CLIENT_SECRET<\/code> values with the values from the XAA testing site.<\/p>\n\n<p>The app should now run, but it still won\u2019t make a successful cross-app access request. Serve the app using the command shown:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>npm start\n<\/code><\/pre><\/div><\/div>\n\n<p>Navigate to <a href=\"http:\/\/localhost:3000\">http:\/\/localhost:3000<\/a>. You should see a landing page that looks like this:<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/xaa-client\/notes-app-fab860469237f856ce30c43a5701eb5b3d3d2d578cbfa873bcc4c1879315153c.jpg\" alt=\"The notes app landing page with a log in button in the top header\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>Feel free to sign in. You\u2019re redirected to the XAA testing site\u2019s IdP for the user challenge. Enter the email address and any combination of numbers for the one-time password. You\u2019ll redirect to the notes view and see something like this:<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/xaa-client\/notes-start-ed0d8d62e286046a35c7dc90aac2f6eb1a9b2dea0416bc99185471b0c0dc80c7.jpg\" alt=\"The notes app after signing in. The left nav has notes, the middle section displays the selected note, and the right side shows an empty todo pane\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>There are no todos yet, and in the IDE\u2019s console we see logging and errors. Each request and response to the XAA testing site\u2019s components has a corresponding log entry. We see the IdP\u2019s redirect with the authorization code, the <code class=\"language-plaintext highlighter-rouge\">POST<\/code> to get tokens along with the request params, and a request to the todo API, which returns a <code class=\"language-plaintext highlighter-rouge\">401 Unauthorized<\/code> HTTP status code. We need to add the code for the XAA token exchange. Stop serving the app by entering <kbd>Ctrl<\/kbd>+<kbd>C<\/kbd> in the terminal.<\/p>\n\n<h2 id=\"exchanging-an-id-token-for-an-access-token-for-another-app\">Exchanging an ID token for an access token for another app<\/h2>\n\n<p>When you sign in to the note-taking app, the IdP issues an ID token. From here, the XAA token flow is a two-step process:<\/p>\n<ol>\n  <li>The note-taking app requests the IDP\u2019s OAuth authorization server to exchange the ID token for a trustworthy intermediary token type, an Identity Assertion JSON Web Token (JWT) also known as ID-JAG, that the todo app recognizes and supports.<\/li>\n  <li>The todo app\u2019s OAuth authorization server exchanges the intermediary token and issues an access token.<\/li>\n<\/ol>\n\n<p>With the access token in hand, the note-taking app can make resource requests to the todo app\u2019s resource server.<\/p>\n\n<p>First, we request the trustworthy intermediary token type, the ID-JAG token.<\/p>\n\n<h3 id=\"exchange-the-id-token-for-an-intermediary-id-jag-token-type\">Exchange the ID token for an intermediary ID-JAG token type<\/h3>\n\n<p>In the IDE, open the <code class=\"language-plaintext highlighter-rouge\">src\/auth\/auth.service.ts<\/code> file. This file contains code for authentication and the OAuth exchange, along with some utility functions. You already have the code to sign in and have the ID token. We\u2019ll continue using the <code class=\"language-plaintext highlighter-rouge\">openid-client<\/code> library for the XAA token exchanges. Find the private helper method <code class=\"language-plaintext highlighter-rouge\">exchangeIdTokenForIdJag()<\/code>. The body of the method has a comment:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ add logic to return an ID-JAG token given the user's ID token<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>We need to replace the inner workings of this method to return the ID-JAG token instead of an empty promise. No empty promises for us! Our promises are as good as tokens. \ud83d\udc7b<\/p>\n\n<p>Replace the code within the method as shown, then I\u2019ll walk through each code block.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cm\">\/**\n * Exchange ID token for ID-JAG token (step 1 of ID-JAG flow)\n *\/<\/span>\n<span class=\"k\">private<\/span> <span class=\"k\">async<\/span> <span class=\"nx\">exchangeIdTokenForIdJag<\/span><span class=\"p\">(<\/span>\n  <span class=\"nx\">config<\/span><span class=\"p\">:<\/span> <span class=\"nx\">openidClient<\/span><span class=\"p\">.<\/span><span class=\"nx\">Configuration<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">idToken<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">authServerUrl<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">resourceUrl<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">scope<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">[],<\/span>\n<span class=\"p\">):<\/span> <span class=\"nb\">Promise<\/span><span class=\"o\">&lt;<\/span><span class=\"kr\">string<\/span><span class=\"o\">&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"nx\">tokenExchangeParams<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">requested_token_type<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">urn:ietf:params:oauth:token-type:id-jag<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">audience<\/span><span class=\"p\">:<\/span> <span class=\"nx\">authServerUrl<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">resource<\/span><span class=\"p\">:<\/span> <span class=\"nx\">resourceUrl<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">subject_token<\/span><span class=\"p\">:<\/span> <span class=\"nx\">idToken<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">subject_token_type<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">urn:ietf:params:oauth:token-type:id_token<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">scope<\/span><span class=\"p\">:<\/span> <span class=\"nx\">scope<\/span><span class=\"p\">.<\/span><span class=\"nx\">join<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\"> <\/span><span class=\"dl\">'<\/span><span class=\"p\">),<\/span>\n  <span class=\"p\">};<\/span>\n\n  <span class=\"kd\">const<\/span> <span class=\"nx\">tokenExchangeResponse<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">openidClient<\/span><span class=\"p\">.<\/span><span class=\"nx\">genericGrantRequest<\/span><span class=\"p\">(<\/span>\n    <span class=\"nx\">config<\/span><span class=\"p\">,<\/span>\n    <span class=\"dl\">'<\/span><span class=\"s1\">urn:ietf:params:oauth:grant-type:token-exchange<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">tokenExchangeParams<\/span><span class=\"p\">,<\/span>\n  <span class=\"p\">);<\/span>\n\n  <span class=\"k\">return<\/span> <span class=\"nx\">tokenExchangeResponse<\/span><span class=\"p\">.<\/span><span class=\"nx\">access_token<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>In this first exchange, we call the IdP. The IdP acts as the broker between the two apps as it\u2019s the trusted source.<\/p>\n\n<p>Let\u2019s step through the key parts of the first code block where we set the token exchange parameters:<\/p>\n<ul>\n  <li><strong><code class=\"language-plaintext highlighter-rouge\">requested_token_type<\/code><\/strong> - we\u2019re asking the IDP for the ID-JAG token<\/li>\n  <li><strong><code class=\"language-plaintext highlighter-rouge\">audience<\/code><\/strong> and <strong><code class=\"language-plaintext highlighter-rouge\">resource<\/code><\/strong> - the authorization server and the todo API we\u2019re requesting resources from<\/li>\n  <li><strong><code class=\"language-plaintext highlighter-rouge\">subject_token<\/code><\/strong> - the token we\u2019re using for this exchange<\/li>\n  <li><strong><code class=\"language-plaintext highlighter-rouge\">subject_token_type<\/code><\/strong> - the type of the token we\u2019re using for the exchange<\/li>\n  <li><strong><code class=\"language-plaintext highlighter-rouge\">scopes<\/code><\/strong> - the requested scopes, such as reading todos<\/li>\n<\/ul>\n\n<p>Once we have all these parameters set, we can call the IdP. The <code class=\"language-plaintext highlighter-rouge\">openid-client<\/code> library has a function for making generic grant requests. We can use it to request the token exchange grant type. While the return value is not an access token, the grant request relies on existing OAuth models that defined the <code class=\"language-plaintext highlighter-rouge\">access_token<\/code> response parameter.<\/p>\n\n<p>Let\u2019s call the method so we can test it out. Find the comment:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>\/\/ Step 1: Exchange ID token for ID-JAG token\n<\/code><\/pre><\/div><\/div>\n<p>in the <code class=\"language-plaintext highlighter-rouge\">exchangeIdTokenForAccessToken()<\/code> method.<\/p>\n\n<p>Add the call to the method like this:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ Step 1: Exchange ID token for ID-JAG token<\/span>\n<span class=\"kd\">const<\/span> <span class=\"nx\">idJagToken<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"k\">this<\/span><span class=\"p\">.<\/span><span class=\"nx\">exchangeIdTokenForIdJag<\/span><span class=\"p\">(<\/span>\n  <span class=\"nx\">idpConfig<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">idToken<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">authServerUrl<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">resourceUrl<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">scope<\/span><span class=\"p\">,<\/span>\n<span class=\"p\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>We\u2019re adding configuration information, including the IdP, client ID, and client secret. And we have some other required configuration values pulled from the <code class=\"language-plaintext highlighter-rouge\">.env<\/code> file, such as the servers for the todo app and the scopes.<\/p>\n\n<p>We\u2019ll get the signed Identity Assertion JWT Authorization grant when the call succeeds. This is a signed token from the IdP, so whenever we exchange it in the next step, the recipient knows it\u2019s trustworthy. Step one complete. \u2705<\/p>\n\n<p>Feel free to start the app and check the console log for your first exchange request. You should see the call to <code class=\"language-plaintext highlighter-rouge\">LOG [OAuth HTTP] \u2192 POST idp.xaa.dev\/token<\/code> in the console. Below that, you\u2019ll see the token exchange parameters that look something like this:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>DEBUG [OAuth HTTP]   body:\n    requested_token_type=urn:ietf:params:oauth:token-type:id-jag\n    audience=https:\/\/auth.resource.xaa.dev\n    resource=https:\/\/api.resource.xaa.dev\n    subject_token=eyJhbGc...IdoRppJyZmV9Q\n    subject_token_type=urn:ietf:params:oauth:token-type:id_token\n    scope=todos.read\n    grant_type=urn:ietf:params:oauth:grant-type:token-exchange\n<\/code><\/pre><\/div><\/div>\n\n<p>The call to get todos will still fail, but you can see the first exchange request in action! \ud83d\ude80<\/p>\n\n<h3 id=\"use-the-id-jag-token-to-request-an-access-token-for-a-separate-app\">Use the ID-JAG token to request an access token for a separate app<\/h3>\n\n<p>With the ID-JAG token in hand, we can now move on to the second exchange, exchanging the ID-JAG intermediary token for an access token to the todo app. We make this exchange with the todo app\u2019s OAuth authorization server. The IdP oversees both the note-taking app and the todo app, and trust domains between the two apps facilitate this flow. Remember, in our first exchange, we had to specify the audience for the ID-JAG token in our request - the todo app.<\/p>\n\n<p>Back in <code class=\"language-plaintext highlighter-rouge\">src\/auth\/auth.service.ts<\/code>, find the comment:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>\/\/ add logic to return an access token given the ID-JAG token\n<\/code><\/pre><\/div><\/div>\n\n<p>This comment is in the placeholder code for the <code class=\"language-plaintext highlighter-rouge\">exchangeIdJagForAccessToken()<\/code> method.<\/p>\n\n<p>Replace the placeholder code to make the exchange. Your code will look like this:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cm\">\/**\n  * Exchange ID-JAG token for access token (step 2 of ID-JAG flow)\n  *\/<\/span>\n<span class=\"k\">private<\/span> <span class=\"k\">async<\/span> <span class=\"nx\">exchangeIdJagForAccessToken<\/span><span class=\"p\">(<\/span>\n  <span class=\"nx\">config<\/span><span class=\"p\">:<\/span> <span class=\"nx\">openidClient<\/span><span class=\"p\">.<\/span><span class=\"nx\">Configuration<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">idJagToken<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">scope<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">[],<\/span>\n<span class=\"p\">):<\/span> <span class=\"nb\">Promise<\/span><span class=\"o\">&lt;<\/span><span class=\"kr\">string<\/span><span class=\"o\">&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"nx\">jwtBearerParams<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">assertion<\/span><span class=\"p\">:<\/span> <span class=\"nx\">idJagToken<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">scope<\/span><span class=\"p\">:<\/span> <span class=\"nx\">scope<\/span><span class=\"p\">.<\/span><span class=\"nx\">join<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\"> <\/span><span class=\"dl\">'<\/span><span class=\"p\">),<\/span>\n  <span class=\"p\">};<\/span>\n\n  <span class=\"kd\">const<\/span> <span class=\"nx\">resourceTokenResponse<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">openidClient<\/span><span class=\"p\">.<\/span><span class=\"nx\">genericGrantRequest<\/span><span class=\"p\">(<\/span>\n    <span class=\"nx\">config<\/span><span class=\"p\">,<\/span>\n    <span class=\"dl\">'<\/span><span class=\"s1\">urn:ietf:params:oauth:grant-type:jwt-bearer<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">jwtBearerParams<\/span><span class=\"p\">,<\/span>\n  <span class=\"p\">);<\/span>\n\n  <span class=\"k\">return<\/span> <span class=\"nx\">resourceTokenResponse<\/span><span class=\"p\">.<\/span><span class=\"nx\">access_token<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>We\u2019re following a similar pattern to the first exchange, with a difference in the grant request. This time, the parameters include an assertion, the ID-JAG token. And we make the grant request to the todo app\u2019s OAuth authorization server with the <code class=\"language-plaintext highlighter-rouge\">urn:ietf:params:oauth:grant-type:jwt-bearer<\/code> grant type. This exchange relies upon a pre-existing spec where one can use a bearer JWT for as a grant type to request an access token. That\u2019s what we\u2019re doing in this step.<\/p>\n\n<p>Next, we\u2019ll call this method in <code class=\"language-plaintext highlighter-rouge\">exchangeIdTokenForAccessToken()<\/code>.<\/p>\n\n<p>Find the comment:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>\/\/ Step 2: Exchange ID-JAG token for access token\n<\/code><\/pre><\/div><\/div>\n\n<p>Because we\u2019re calling a new authorization server, the todo app\u2019s OAuth authorization server, we first need to read the well-known discovery docs. The discovery docs include information about the authorization server, such as the server\u2019s capabilities and endpoints, including the token endpoint. Since we\u2019re authenticating with the todo app\u2019s authorization server, not the IdP, we use the resource app\u2019s credentials here. The todo app\u2019s authorization server recognizes RESOURCE_CLIENT_ID and RESOURCE_CLIENT_SECRET, not your notes app\u2019s credentials. We\u2019ve been using a custom <code class=\"language-plaintext highlighter-rouge\">fetch<\/code> implementation to capture the logging you see, so we must include that implementation in <code class=\"language-plaintext highlighter-rouge\">openid-client<\/code> too. Then make the call to the <code class=\"language-plaintext highlighter-rouge\">exchangeIdJagForAccessToken()<\/code> helper method. Your code will look like this:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ Step 2: Exchange ID-JAG token for access token<\/span>\n<span class=\"kd\">const<\/span> <span class=\"nx\">resourceAuthConfig<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">openidClient<\/span><span class=\"p\">.<\/span><span class=\"nx\">discovery<\/span><span class=\"p\">(<\/span>\n  <span class=\"k\">new<\/span> <span class=\"nx\">URL<\/span><span class=\"p\">(<\/span><span class=\"nx\">authServerUrl<\/span><span class=\"p\">),<\/span>\n  <span class=\"nx\">resourceClientId<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">resourceClientSecret<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">openidClient<\/span><span class=\"p\">.<\/span><span class=\"nx\">ClientSecretPost<\/span><span class=\"p\">(<\/span><span class=\"nx\">resourceClientSecret<\/span> <span class=\"o\">??<\/span> <span class=\"dl\">''<\/span><span class=\"p\">),<\/span>\n<span class=\"p\">);<\/span>\n<span class=\"nx\">resourceAuthConfig<\/span><span class=\"p\">[<\/span><span class=\"nx\">openidClient<\/span><span class=\"p\">.<\/span><span class=\"nx\">customFetch<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">loggedFetch<\/span><span class=\"p\">;<\/span>\n\n<span class=\"k\">return<\/span> <span class=\"k\">this<\/span><span class=\"p\">.<\/span><span class=\"nx\">exchangeIdJagForAccessToken<\/span><span class=\"p\">(<\/span>\n  <span class=\"nx\">resourceAuthConfig<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">idJagToken<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">scope<\/span><span class=\"p\">,<\/span>\n<span class=\"p\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Make sure to remove any placeholder implementation. Step two complete. \u2705<\/p>\n\n<p>The code to make a request to the todo API using the bearer token already exists in the project. Let\u2019s try running the app now using <code class=\"language-plaintext highlighter-rouge\">npm start<\/code>.<\/p>\n\n<h2 id=\"inspecting-the-xaa-token-exchange\">Inspecting the XAA token exchange<\/h2>\n\n<p>After you authenticate, you\u2019ll see the notes and the todos! \ud83c\udf89<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/xaa-client\/notes-todos-631e2643399d8e41702e2d3460371f7d6e112fc1c0f03a7b097aec8e5217e649.jpg\" alt=\"The notes app with todos listed on the side\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>In the terminal console, you\u2019ll see each step of the handshake and requests:<\/p>\n\n<ol>\n  <li>Authentication in the notes app with the IdP returning the ID token<\/li>\n  <li>Exchanging the ID token for an ID-JAG token with the IDP\u2019s OAuth authorization server<\/li>\n  <li>Exchanging the ID-JAG token for an access token with the todo app\u2019s OAuth authorization server<\/li>\n  <li>Call the todo app\u2019s resource server (the API)<\/li>\n<\/ol>\n\n<p>Feel free to inspect each step of this flow, the request parameters, and the responses.<\/p>\n\n<p>These steps allow an app to make requests to a third-party app within enterprise systems securely. You can find the completed project <a href=\"https:\/\/github.com\/oktadev\/okta-js-xaa-requestor-example\">in the GitHub repo<\/a> with instructions to also test on <a href=\"https:\/\/github.com\/features\/codespaces\">GitHub Codespaces<\/a>.<\/p>\n\n<h2 id=\"learn-more-about-xaa-and-elevating-identity-security-using-oauth\">Learn more about XAA and elevating identity security using OAuth<\/h2>\n\n<p>I hope you enjoyed this post on making secure cross-app requests for enterprise use cases. If you found this post interesting, I encourage you to check out these links:<\/p>\n\n<ul>\n  <li><a href=\"\/blog\/2025\/09\/03\/cross-app-access\">Build Secure Agent-to-App Connections with Cross App Access (XAA)<\/a><\/li>\n  <li><a href=\"https:\/\/drafts.oauth.net\/oauth-identity-assertion-authz-grant\/draft-ietf-oauth-identity-assertion-authz-grant.html\">Identity Assertion JWT Authorization Grant<\/a><\/li>\n  <li><a href=\"\/blog\/2024\/04\/30\/express-universal-logout\">How to Instantly Sign a User Out across All Your Apps<\/a><\/li>\n  <li><a href=\"\/blog\/2024\/02\/29\/net-scim\">How to Manage User Lifecycle with .NET and SCIM<\/a><\/li>\n  <li><a href=\"\/blog\/2023\/09\/25\/oauth-api-tokens\">Why You Should Migrate to OAuth 2.0 From Static API Tokens<\/a><\/li>\n<\/ul>\n\n<p>Remember to follow us on <a href=\"https:\/\/twitter.com\/oktadev\">Twitter<\/a> and subscribe to our <a href=\"https:\/\/www.youtube.com\/c\/OktaDev\/\">YouTube channel<\/a> for more exciting content. We also want to hear from you about the topics you\u2019d like to see and any questions you may have. Leave us a comment below!<\/p>\n","pubDate":"Tue, 10 Feb 2026 00:00:00 -0500","link":"https:\/\/developer.okta.com\/blog\/2026\/02\/10\/xaa-client","guid":"https:\/\/developer.okta.com\/blog\/2026\/02\/10\/xaa-client"},{"title":"Take User Provisioning to the Next Level with Entitlements","description":"<p>When you work on B2B SaaS apps used by large customer organizations, synchronizing those customers\u2019 users within your software system is tricky! You must synchronize user profile information and the user attributes required for access control management. Customers with large workforces may have thousands of users to manage. They demand a speedy onboarding process, including automated user provisioning from their identity provider!<\/p>\n\n<p>Managing users across domains is critical to making B2B apps enterprise-scalable. In the <a href=\"\/blog\/tags\/enterprise-ready-workshops\/\">Enterprise-Ready and Enterprise-Maturity on-demand workshop series<\/a>, we tackle the dilemmas faced by developers of SaaS products wanting to scale their apps to enterprise customers. We iterate on a fictitious B2B Todo app more secure and capable for enterprise customers using industry-recognized standards such as OpenID Connect (OIDC) authentication and System for Cross-Domain Identity Management (SCIM) for user provisioning. In this workshop, you build upon a previous workshop introducing automated user provisioning to add support for users\u2019 access management and permissions attributes\u2014their entitlements.<\/p>\n\n<table>\n<tr>\n    <td style=\"font-size: 3rem;\">\ufe0f\u2139\ufe0f<\/td>\n    <td>\n      <strong>Note<\/strong> <br \/>\n    This post requires Okta Identity Governance (OIG) features in your Okta org. <a href=\"https:\/\/developer.okta.com\/signup\/\">Sign up for a new Integrator Free plan<\/a> to continue.\n    <\/td>\n<\/tr>\n<\/table>\n\n<table>\n  <thead>\n    <tr>\n      <th>Posts in the on-demand workshop series<\/th>\n    <\/tr>\n  <\/thead>\n  <tbody>\n    <tr>\n      <td>1. <a href=\"\/blog\/2023\/07\/27\/enterprise-ready-getting-started\">How to Get Going with the On-Demand SaaS Apps Workshops<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>2. <a href=\"\/blog\/2023\/07\/28\/oidc_workshop\">Enterprise-Ready Workshop: Authenticate with OpenID Connect<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>3. <a href=\"\/blog\/2023\/07\/28\/scim-workshop\">Enterprise-Ready Workshop: Manage Users with SCIM<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>4. <a href=\"\/blog\/2023\/07\/28\/terraform-workshop\">Enterprise Maturity Workshop: Terraform<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>5. <a href=\"\/blog\/2023\/09\/15\/workflows-workshop\">Enterprise Maturity Workshop: Automate with no-code Okta Workflows<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>6. <a href=\"\/blog\/2024\/04\/30\/express-universal-logout\">How to Instantly Sign a User Out across All Your Apps<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>7. <strong>Take User Provisioning to the Next Level with Entitlements<\/strong><\/td>\n    <\/tr>\n  <\/tbody>\n<\/table>\n\n<p>This workshop walks you through adding the code to support entitlements in a sample application with three broad sections:<\/p>\n<ol>\n  <li>Introduction to the base application, tools, and the development process<\/li>\n  <li>See your application\u2019s user and entitlements information in Okta<\/li>\n  <li>Use Okta to manage user roles and custom entitlements<\/li>\n<\/ol>\n\n<p>If you want to skip to the completed code project for this workshop, you can find it in the <a href=\"https:\/\/github.com\/oktadev\/okta-enterprise-ready-workshops\/tree\/entitlements-workshop-complete\"><code class=\"language-plaintext highlighter-rouge\">entitlements-completed<\/code> branch on the GitHub repo<\/a>.<\/p>\n\n<p><strong class=\"hide\">Table of Contents<\/strong><\/p>\n<ul id=\"markdown-toc\">\n  <li><a href=\"#manage-users-at-scale-using-system-for-cross-domain-identity-management-scim\" id=\"markdown-toc-manage-users-at-scale-using-system-for-cross-domain-identity-management-scim\">Manage users at scale using System for Cross-domain Identity Management (SCIM)<\/a>    <ul>\n      <li><a href=\"#prepare-the-expressjs-api-project\" id=\"markdown-toc-prepare-the-expressjs-api-project\">Prepare the Express.js API project<\/a><\/li>\n      <li><a href=\"#serve-the-expressjs-api-and-test-the-roles-scim-endpoint\" id=\"markdown-toc-serve-the-expressjs-api-and-test-the-roles-scim-endpoint\">Serve the Express.js API and test the <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code> SCIM endpoint<\/a><\/li>\n    <\/ul>\n  <\/li>\n  <li><a href=\"#support-user-roles-in-the-database\" id=\"markdown-toc-support-user-roles-in-the-database\">Support user roles in the database<\/a><\/li>\n  <li><a href=\"#connect-okta-to-the-scim-server\" id=\"markdown-toc-connect-okta-to-the-scim-server\">Connect Okta to the SCIM server<\/a><\/li>\n  <li><a href=\"#create-an-okta-scim-application-for-entitlements-governance\" id=\"markdown-toc-create-an-okta-scim-application-for-entitlements-governance\">Create an Okta SCIM application for entitlements governance<\/a><\/li>\n  <li><a href=\"#scim-schemas-and-resources\" id=\"markdown-toc-scim-schemas-and-resources\">SCIM schemas and resources<\/a>    <ul>\n      <li><a href=\"#harness-typescript-to-conform-to-scim-schemas\" id=\"markdown-toc-harness-typescript-to-conform-to-scim-schemas\">Harness TypeScript to conform to SCIM schemas<\/a><\/li>\n      <li><a href=\"#scim-list-response\" id=\"markdown-toc-scim-list-response\">SCIM list response<\/a><\/li>\n      <li><a href=\"#return-database-defined-roles-in-the-scim-roles-endpoint\" id=\"markdown-toc-return-database-defined-roles-in-the-scim-roles-endpoint\">Return database-defined roles in the SCIM <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code> endpoint<\/a><\/li>\n    <\/ul>\n  <\/li>\n  <li><a href=\"#scim-resource-types\" id=\"markdown-toc-scim-resource-types\">SCIM resource types<\/a><\/li>\n  <li><a href=\"#add-roles-to-the-scim-users-endpoints\" id=\"markdown-toc-add-roles-to-the-scim-users-endpoints\">Add roles to the SCIM Users endpoints<\/a>    <ul>\n      <li><a href=\"#update-the-scim-add-users-call-to-include-roles\" id=\"markdown-toc-update-the-scim-add-users-call-to-include-roles\">Update the SCIM add users call to include roles<\/a><\/li>\n      <li><a href=\"#add-roles-when-getting-a-list-of-users-in-scim\" id=\"markdown-toc-add-roles-when-getting-a-list-of-users-in-scim\">Add roles when getting a list of users in SCIM<\/a><\/li>\n      <li><a href=\"#update-the-users-call-so-scim-clients-can-set-their-roles\" id=\"markdown-toc-update-the-users-call-so-scim-clients-can-set-their-roles\">Update the <code class=\"language-plaintext highlighter-rouge\">\/Users<\/code> call so SCIM clients can set their roles<\/a><\/li>\n    <\/ul>\n  <\/li>\n  <li><a href=\"#entitlements-discovery-in-okta\" id=\"markdown-toc-entitlements-discovery-in-okta\">Entitlements discovery in Okta<\/a>    <ul>\n      <li><a href=\"#syncing-user-entitlements\" id=\"markdown-toc-syncing-user-entitlements\">Syncing user entitlements<\/a><\/li>\n      <li><a href=\"#schema-discovery-for-custom-entitlements\" id=\"markdown-toc-schema-discovery-for-custom-entitlements\">Schema discovery for custom entitlements<\/a><\/li>\n    <\/ul>\n  <\/li>\n  <li><a href=\"#multi-tenant-use-cases-for-entitlements\" id=\"markdown-toc-multi-tenant-use-cases-for-entitlements\">Multi-tenant use cases for entitlements<\/a><\/li>\n  <li><a href=\"#use-scim-to-manage-user-provisioning-and-entitlements\" id=\"markdown-toc-use-scim-to-manage-user-provisioning-and-entitlements\">Use SCIM to manage user provisioning and entitlements<\/a><\/li>\n<\/ul>\n\n<h2 id=\"manage-users-at-scale-using-system-for-cross-domain-identity-management-scim\">Manage users at scale using System for Cross-domain Identity Management (SCIM)<\/h2>\n\n<p>The Todo app tech stack uses a React frontend and an Express API backend. For this workshop, you need the following required tooling:<\/p>\n\n<p><strong>Required tools<\/strong><\/p>\n<ul>\n  <li><a href=\"https:\/\/nodejs.org\/en\">Node.js<\/a> v18 or higher<\/li>\n  <li>Command-line terminal application<\/li>\n  <li>A code editor\/Integrated development environment (IDE), such as <a href=\"https:\/\/code.visualstudio.com\/\">Visual Studio Code<\/a> (VS Code)<\/li>\n  <li>An HTTP client testing tool, such as <a href=\"https:\/\/www.postman.com\/\">Postman<\/a> or the <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=mkloubert.vscode-http-client\">HTTP Client<\/a> VS Code extension<\/li>\n<\/ul>\n\n<blockquote>\n  <p>VS Code has integrated terminals and HTTP client extensions that allow you to work out of this one application for almost everything required in this workshop. The IDE also supports TypeScript, so you\u2019ll get quicker responses on type errors and help with importing modules.<\/p>\n<\/blockquote>\n\n<p>Follow the instructions in the getting started guide for installing the required tools and serving the Todo application.<\/p>\n\n<article class=\"link-container\" style=\"border: 1px solid silver; border-radius: 3px; padding: 12px 15px\">\n              <a href=\"\/blog\/2023\/07\/27\/enterprise-ready-getting-started\" style=\"font-size: 1.375em; margin-bottom: 20px;\">\n                <span>How to Get Going with the On-Demand SaaS Apps Workshops<\/span>\n              <\/a>\n              <p>Start your journey to identity maturity for your SaaS applications in the enterprise-ready workshops! This post covers installing and running the base application in preparation for the upcoming workshops.<\/p>\n              <div><div class=\"BlogPost-attribution\">\n            <a href=\"\/blog\/authors\/alisa-duncan\/\">\n              <img src=\"\/assets-jekyll\/avatar-alisa_duncan-b29fa4df50f5c99f536307c6bc0e5cb3434a922bdada7fe4f4b3cf8488299465.jpg\" alt=\"avatar-avatar-alisa_duncan.jpeg\" class=\"BlogPost-avatar\" \/>\n            <\/a>\n            <span class=\"BlogPost-author\">\n                <a href=\"\/blog\/authors\/alisa-duncan\/\">Alisa Duncan<\/a>\n            <\/span>\n          <\/div><\/div>\n          <\/article>\n\n<p>You\u2019ll build upon a prior workshop introducing syncing users across systems using the <a href=\"https:\/\/scim.cloud\/\">System for Cross-domain Identity Management<\/a> (SCIM) protocol.<\/p>\n\n<p>In this workshop, you\u2019ll dive deeper into automated user provisioning by adding the user attributes required for access management, such as user roles, licensing, permissions, or something else you use to denote what actions a user has access to. The access management attributes of users are known by the generic term, user entitlements. Then, we will continue diving deeper into supporting customized user entitlements using the SCIM protocol.<\/p>\n\n<p>Before we get going with user entitlements, you\u2019ll first step through the interactive and fun <a href=\"\/blog\/2023\/07\/28\/scim-workshop\">Enterprise-Ready Workshop: Manage Users with SCIM<\/a> workshop to get the SCIM overview, set up the code and your Okta account, and see how the protocol works. I\u2019ll settle down with a cup of tea and a good book and wait while you learn about SCIM and are ready to continue! \ud83e\uded6\ud83c\udf75\ud83d\udcda<\/p>\n\n<article class=\"link-container\" style=\"border: 1px solid silver; border-radius: 3px; padding: 12px 15px\">\n              <a href=\"\/blog\/2023\/07\/28\/scim-workshop\" style=\"font-size: 1.375em; margin-bottom: 20px;\">\n                <span>Enterprise-Ready Workshop: Manage users with SCIM<\/span>\n              <\/a>\n              <p>In this workshop, you will add SCIM support to a sample application, so that user changes made in your app can sync to your customer's Identity Provider!<\/p>\n              <div><div class=\"BlogPost-attribution\">\n            <a href=\"\/blog\/authors\/semona-igama\/\">\n              <img src=\"\/assets-jekyll\/avatar-semona-igama-03eb4c28aca3765f862b574e032d32f6f8186d04ae9f0db75bed9c74f48a9a3f.jpg\" alt=\"avatar-avatar-semona-igama.jpeg\" class=\"BlogPost-avatar\" \/>\n            <\/a>\n            <span class=\"BlogPost-author\">\n                <a href=\"\/blog\/authors\/semona-igama\/\">Semona Igama<\/a>\n            <\/span>\n          <\/div><\/div>\n          <\/article>\n\n<h3 id=\"prepare-the-expressjs-api-project\">Prepare the Express.js API project<\/h3>\n\n<p>Start from a clean code project by using the SCIM workshop\u2019s completed project code from the <a href=\"https:\/\/github.com\/oktadev\/okta-enterprise-ready-workshops\/tree\/scim-workshop-complete\">scim-workshop-complete<\/a> branch. I\u2019ll post the instructions using <a href=\"https:\/\/git-scm.com\/\">Git<\/a>, but you can download the code as <a href=\"https:\/\/github.com\/oktadev\/okta-enterprise-ready-workshops\/archive\/refs\/heads\/scim-workshop-complete.zip\">a zip file<\/a> if you prefer and skip the Git command.<\/p>\n\n<p>Get a local copy of the completed SCIM workshop code and install dependencies by running the following commands in your terminal:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>git clone <span class=\"nt\">-b<\/span> scim-workshop-complete https:\/\/github.com\/oktadev\/okta-enterprise-ready-workshops.git\n<span class=\"nb\">cd <\/span>okta-enterprise-ready-workshops\nnpm ci\n<\/code><\/pre><\/div><\/div>\n\n<p>Open the code project in your IDE. We\u2019ll work exclusively within the Express.js API for this project, and the code files for the API are in the <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src<\/code> directory.<\/p>\n\n<p>Create a file named <code class=\"language-plaintext highlighter-rouge\">entitlements.ts<\/code>. We\u2019ll define the API routes for user entitlements in the <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/entitlements.ts<\/code> file.<\/p>\n\n<p>Let\u2019s start by hard-coding an API endpoint for <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code> that returns a list of roles. In the <code class=\"language-plaintext highlighter-rouge\">entitlements.ts<\/code> file, add the following code:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">Router<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">express<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n\n<span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">rolesRoute<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">Router<\/span><span class=\"p\">();<\/span>\n\n<span class=\"nx\">rolesRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">route<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/<\/span><span class=\"dl\">'<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">.<\/span><span class=\"kd\">get<\/span><span class=\"p\">(<\/span><span class=\"k\">async<\/span> <span class=\"p\">(<\/span><span class=\"nx\">req<\/span><span class=\"p\">,<\/span> <span class=\"nx\">res<\/span><span class=\"p\">)<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"nx\">roles<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span>\n    <span class=\"dl\">'<\/span><span class=\"s1\">Todo-er<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"dl\">'<\/span><span class=\"s1\">Admin<\/span><span class=\"dl\">'<\/span>\n  <span class=\"p\">];<\/span>\n\n  <span class=\"k\">return<\/span> <span class=\"nx\">res<\/span><span class=\"p\">.<\/span><span class=\"nx\">json<\/span><span class=\"p\">(<\/span><span class=\"nx\">roles<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/scim.ts<\/code>. We need to register the endpoint in the Express app by including it as part of the SCIM routes.<\/p>\n\n<p>At the top of the file, import <code class=\"language-plaintext highlighter-rouge\">rolesRoutes<\/code><\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">rolesRoute<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">.\/entitlements<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>At the bottom of the file below the existing code, add<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">scimRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">use<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/Roles<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"nx\">rolesRoute<\/span><span class=\"p\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>to register the endpoint. Let\u2019s make sure everything works!<\/p>\n\n<h3 id=\"serve-the-expressjs-api-and-test-the-roles-scim-endpoint\">Serve the Express.js API and test the <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code> SCIM endpoint<\/h3>\n\n<p>In the terminal, start the API by running<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>npm run serve-api\n<\/code><\/pre><\/div><\/div>\n\n<p>This command serves the API on port 3333. Launch your HTTP client and call the <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code> endpoint:<\/p>\n\n<div class=\"language-http highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nf\">GET<\/span> <span class=\"nn\">http:\/\/localhost:3333\/scim\/v2\/Roles<\/span> <span class=\"k\">HTTP<\/span><span class=\"o\">\/<\/span><span class=\"m\">1.1<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Do you see a successful response with a list of roles?<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>HTTP\/1.1 200 OK\n\n[\n  \"Todo-er\",\n  \"Admin\"\n]\n<\/code><\/pre><\/div><\/div>\n\n<p>Take a look at the terminal output. You\u2019ll see output recording the <code class=\"language-plaintext highlighter-rouge\">GET<\/code> request!<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/user-entitlements-workshop\/morgan-968d824c03d48283182cce88f47f8163b3a9af02a9b98ed786a98e1a24589296.jpg\" alt=\"Terminal output showing the GET request to the \/Roles route and a 200OK HTTP response\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>The project uses <a href=\"https:\/\/github.com\/expressjs\/morgan\">Morgan<\/a>, a library that automatically adds HTTP logging to the Express API. The terminal output includes <code class=\"language-plaintext highlighter-rouge\">POST<\/code> and <code class=\"language-plaintext highlighter-rouge\">PUT<\/code> request payloads, so it\u2019s an excellent way to track the SCIM calls as you work through the workshop.<\/p>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">npm run serve-api<\/code> process watches for changes and automatically updates the API, so we don\u2019t need to stop and restart it constantly. But we\u2019re about to make some significant changes. Stop serving the API by entering <kbd>Ctrl<\/kbd>+<kbd>c<\/kbd> in the terminal so we can prepare the database.<\/p>\n\n<h2 id=\"support-user-roles-in-the-database\">Support user roles in the database<\/h2>\n\n<p>The Todo app database needs to support roles; we\u2019ve hardcoded roles so far. It\u2019s time to bring the database to the party. A fancier SaaS app might allow each customer to define their roles. We\u2019ll skip that level of customizability for now and focus on the simplest case. For this workshop, we\u2019ll define supported roles for all Todo app customers instead of allowing role configurations per organization. Taking the position of application roles instead of organization roles makes our database modeling easier. I\u2019ll discuss ways to add per-organization configurability later in the post.<\/p>\n\n<p>Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/prisma\/schema.prisma<\/code>. Add the role model at the end of the file.<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>model Role {\n  id Int @id @default(autoincrement())\n  name String\n  users User[]\n}\n<\/code><\/pre><\/div><\/div>\n\n<p>A user may have zero or more roles. Update the user model to add roles so that the user model looks like this:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>model User {\n  id         Int    @id @default(autoincrement())\n  email      String\n  password   String?\n  name       String\n  Todo       Todo[]\n  org        Org?    @relation(fields: [orgId], references: [id])\n  orgId      Int?\n  externalId String?\n  active     Boolean?\n  roles      Role[]\n  @@unique([orgId, externalId])\n}\n<\/code><\/pre><\/div><\/div>\n\n<p>With the roles model defined, it\u2019s time to update the database to match the model. We\u2019ll start with a fresh, clean database for this project. In the terminal run<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>npx prisma migrate reset <span class=\"nt\">-f<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>It helps to have some seed data so we can get going. Here, we\u2019ll define roles available within the Todo app. A user can be a \u201cTodo-er,\u201d \u201cTodo Auditor,\u201d and \u201cManager.\u201d Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/prisma\/seed_script.ts<\/code> and replace the entire file with the code below:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">PrismaClient<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">@prisma\/client<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n\n<span class=\"kd\">const<\/span> <span class=\"nx\">prisma<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nx\">PrismaClient<\/span><span class=\"p\">();<\/span>\n\n<span class=\"k\">async<\/span> <span class=\"kd\">function<\/span> <span class=\"nx\">main<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"nx\">org<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">org<\/span><span class=\"p\">.<\/span><span class=\"nx\">create<\/span><span class=\"p\">({<\/span>\n    <span class=\"na\">data<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">domain<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">gridco.example<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">apikey<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">123123<\/span><span class=\"dl\">'<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">});<\/span>\n  <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nx\">log<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">Created org Portal<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"nx\">org<\/span><span class=\"p\">);<\/span>\n\n  <span class=\"c1\">\/\/ Roles defined by the Todo app<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"nx\">roles<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span>\n    <span class=\"p\">{<\/span> <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Todo-er<\/span><span class=\"dl\">'<\/span> <span class=\"p\">},<\/span>\n    <span class=\"p\">{<\/span> <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Todo Auditor<\/span><span class=\"dl\">'<\/span> <span class=\"p\">},<\/span>\n    <span class=\"p\">{<\/span> <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Manager<\/span><span class=\"dl\">'<\/span><span class=\"p\">}<\/span>\n  <span class=\"p\">];<\/span>\n\n  <span class=\"kd\">const<\/span> <span class=\"nx\">createdRoles<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nb\">Promise<\/span><span class=\"p\">.<\/span><span class=\"nx\">all<\/span><span class=\"p\">(<\/span>\n    <span class=\"nx\">roles<\/span><span class=\"p\">.<\/span><span class=\"nx\">map<\/span><span class=\"p\">(<\/span><span class=\"nx\">data<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">create<\/span><span class=\"p\">({<\/span><span class=\"nx\">data<\/span><span class=\"p\">}))<\/span>\n  <span class=\"p\">);<\/span>\n\n  <span class=\"k\">for<\/span> <span class=\"p\">(<\/span><span class=\"kd\">const<\/span> <span class=\"nx\">role<\/span> <span class=\"k\">of<\/span> <span class=\"nx\">createdRoles<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nx\">log<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">Created role <\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"nx\">role<\/span><span class=\"p\">);<\/span>\n  <span class=\"p\">}<\/span>\n\n  <span class=\"kd\">const<\/span> <span class=\"nx\">somnusUser<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">create<\/span><span class=\"p\">({<\/span>\n    <span class=\"na\">data<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Somnus Henderson<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">email<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">somnus.henderson@gridco.example<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">password<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">correct horse battery staple<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">orgId<\/span><span class=\"p\">:<\/span> <span class=\"nx\">org<\/span><span class=\"p\">.<\/span><span class=\"nx\">id<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">externalId<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">31<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">active<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">});<\/span>\n  <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nx\">log<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">Created user Somnus<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"nx\">somnusUser<\/span><span class=\"p\">)<\/span>\n\n <span class=\"kd\">const<\/span> <span class=\"nx\">trinityUser<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">create<\/span><span class=\"p\">({<\/span>\n    <span class=\"na\">data<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Trinity JustTrinity<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">email<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">trinity@gridco.example<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">password<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Zion<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">orgId<\/span><span class=\"p\">:<\/span> <span class=\"nx\">org<\/span><span class=\"p\">.<\/span><span class=\"nx\">id<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">externalId<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">32<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">active<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n        <span class=\"na\">connect<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n          <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"nx\">createdRoles<\/span><span class=\"p\">.<\/span><span class=\"nx\">find<\/span><span class=\"p\">(<\/span><span class=\"nx\">r<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"nx\">r<\/span><span class=\"p\">.<\/span><span class=\"nx\">name<\/span> <span class=\"o\">===<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Todo-er<\/span><span class=\"dl\">'<\/span><span class=\"p\">)?.<\/span><span class=\"nx\">id<\/span>\n        <span class=\"p\">}<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">},<\/span>\n  <span class=\"p\">})<\/span>\n  <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nx\">log<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">Created user Trinity<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"nx\">trinityUser<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"nx\">main<\/span><span class=\"p\">()<\/span>\n  <span class=\"p\">.<\/span><span class=\"nx\">then<\/span><span class=\"p\">(<\/span><span class=\"k\">async<\/span> <span class=\"p\">()<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">$disconnect<\/span><span class=\"p\">()<\/span>\n  <span class=\"p\">})<\/span>\n  <span class=\"p\">.<\/span><span class=\"k\">catch<\/span><span class=\"p\">(<\/span><span class=\"k\">async<\/span> <span class=\"p\">(<\/span><span class=\"nx\">e<\/span><span class=\"p\">)<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nx\">error<\/span><span class=\"p\">(<\/span><span class=\"nx\">e<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">$disconnect<\/span><span class=\"p\">()<\/span>\n    <span class=\"nx\">process<\/span><span class=\"p\">.<\/span><span class=\"nx\">exit<\/span><span class=\"p\">(<\/span><span class=\"mi\">1<\/span><span class=\"p\">)<\/span>\n  <span class=\"p\">})<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Save the file and run the npm script in the terminal to seed the database.<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>npm run init-db\n<\/code><\/pre><\/div><\/div>\n\n<p>You\u2019ll see console output for each newly created database record. \ud83c\udf89<\/p>\n\n<p><strong>Inspect the database records<\/strong><\/p>\n\n<p>You can inspect the database records using <a href=\"https:\/\/www.prisma.io\/studio\">Prisma Studio<\/a>. In a separate terminal, run<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>npx prisma studio\n<\/code><\/pre><\/div><\/div>\n\n<p>which launches a web interface to view the database. The site URL is usually <code class=\"language-plaintext highlighter-rouge\">http:\/\/localhost:5555<\/code>, shown in the terminal output. Open the site in your browser to view the database tables, records, and relationships.<\/p>\n\n<h2 id=\"connect-okta-to-the-scim-server\">Connect Okta to the SCIM server<\/h2>\n\n<p>The SCIM Client (the identity provider, Okta) makes requests upon objects held by the SCIM Server (the Todo app).<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/scim-workshop\/scim-diagram-4cfb2cb57d2031d826a9221b3fdf59284e2df35657a79c53118f3bb776be0440.jpg\" alt=\"SCIM workflow showing the Identity Provider requests the SCIM server with GET, POST, PUT, and DEL user calls and the SCIM server responds with a standard SCIM interface\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>First, we need to serve the API so Okta can access it. You\u2019ll use a temporary tunnel for local development that makes <code class=\"language-plaintext highlighter-rouge\">localhost:3333<\/code> publicly accessible so that Okta, the SCIM client, can call your API, the SCIM server. I\u2019ll include the instructions using an NPM library that we don\u2019t have to install or sign up for, but feel free to use your favorite tunneling system if you have one.<\/p>\n\n<p>You need two terminal sessions.<\/p>\n\n<p>In one terminal, serve the API using the command:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>npm run serve-api\n<\/code><\/pre><\/div><\/div>\n\n<p>In the second terminal, you\u2019ll run the local tunnel. Run the command:<\/p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>npx localtunnel <span class=\"nt\">--port<\/span> 3333\n<\/code><\/pre><\/div><\/div>\n\n<p>This creates a tunnel for the application serving on port 3333. The console output displays the tunnel URL in the format <code class=\"language-plaintext highlighter-rouge\">https:\/\/{yourTunnelSubdomain}.loca.lt<\/code>, such as:<\/p>\n\n<div class=\"language-console highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"go\">your URL is: https:\/\/awesome-devs-club.loca.lt\n<\/span><\/code><\/pre><\/div><\/div>\n\n<p>You\u2019ll need this tunnel URL to configure the Okta application.<\/p>\n\n<h2 id=\"create-an-okta-scim-application-for-entitlements-governance\">Create an Okta SCIM application for entitlements governance<\/h2>\n\n<p>In the prerequisite SCIM workshop, you added a SCIM application in Okta to connect to the Todo app. We must do something similar to connect SCIM with entitlements support.<\/p>\n\n<p>Sign into your <a href=\"https:\/\/developer.okta.com\/login\/\">Okta Integrator Free account<\/a>. In the Admin Console, navigate to <strong>Applications<\/strong> &gt; <strong>Applications<\/strong>. Press the <strong>Browse App Catalog<\/strong> button to create a new Okta SCIM application.<\/p>\n\n<p>In the search bar, search for \u201c(Header Auth) Governance with SCIM 2.0\u201d and select the app. Press <strong>Add Integration<\/strong>.<\/p>\n\n<p>You\u2019ll see a configuration view with two tabs. Press <strong>Next<\/strong> on the <strong>General settings<\/strong> tab. Leave default settings on the <strong>Sign-On Options<\/strong> tab and press <strong>Done<\/strong>.<\/p>\n\n<p>You\u2019ll navigate to your newly created Okta application to add specific configurations about the Todo app.<\/p>\n\n<p>First, you need to enable Identity Governance. Navigate to the <strong>General<\/strong> tab and find the <strong>Identity Governance<\/strong> section. Press <strong>Edit<\/strong> to select <strong>Enabled<\/strong> for <strong>Governance Engine<\/strong>. Remember to <strong>Save<\/strong> your change.<\/p>\n\n<p>Navigate to the <strong>Provisioning<\/strong> tab and press the <strong>Configure API Integration<\/strong> button. Check the <strong>Enable API integration<\/strong> checkbox\u2014two more form fields display.<\/p>\n<ul>\n  <li>\n    <p>In <strong>Base URL<\/strong> field, enter <code class=\"language-plaintext highlighter-rouge\">https:\/\/{yourTunnelSubdomain}.loca.lt\/scim\/v2<\/code>.<\/p>\n\n    <p>It will look like <code class=\"language-plaintext highlighter-rouge\">https:\/\/awesome-devs-club.loca.lt\/scim\/v2<\/code><\/p>\n  <\/li>\n  <li>\n    <p>In the <strong>API Token<\/strong> field, enter <code class=\"language-plaintext highlighter-rouge\">Bearer 123123<\/code><\/p>\n  <\/li>\n<\/ul>\n\n<p>press <strong>Save<\/strong>.<\/p>\n\n<p>The <strong>Provisioning<\/strong> tab has more options to configure within the <strong>Settings<\/strong> side nav.<\/p>\n\n<p>Navigate to the <strong>To App<\/strong> option and press <strong>Edit<\/strong>.<\/p>\n<ul>\n  <li>Enable <strong>Create Users<\/strong><\/li>\n  <li>Enable <strong>Update User Attributes<\/strong><\/li>\n  <li>Enable <strong>Deactivate Users<\/strong><\/li>\n<\/ul>\n\n<p>Press <strong>Save<\/strong>.<\/p>\n\n<p>Import users from the todo app into Okta. Navigate to the <strong>Import<\/strong> tab and press the <strong>Import Now<\/strong> button. Okta discovers users in your app and tries to match them with users already defined in Okta. A dialog shows Okta discovered the two users you added using the DB script. Select both users and press the <strong>Confirm Assignments<\/strong> to confirm the assignments.<\/p>\n\n<p>You\u2019ll see the imported users in the <strong>Assignments<\/strong> tab. But what about entitlements? They\u2019re coming right up!<\/p>\n\n<p>Stop the tunnel and the API using the <kbd>Ctrl<\/kbd>+<kbd>c<\/kbd> command in the terminal windows. We\u2019ll make some changes to the API that won\u2019t automatically reflect in the local tunnel, so we\u2019ll get all our entitlements changes made and resynchronize with Okta.<\/p>\n\n<h2 id=\"scim-schemas-and-resources\">SCIM schemas and resources<\/h2>\n\n<p>In the first SCIM workshop, you learned about SCIM\u2019s <code class=\"language-plaintext highlighter-rouge\">User<\/code> resource and built out operations around the user. You updated only a handful of user properties in the workshop, but SCIM is way more powerful thanks to its superpower \u2013 <em>extensibility<\/em>. \u2728 User is not the only resource type defined in SCIM.<\/p>\n\n<p>A <code class=\"language-plaintext highlighter-rouge\">Resource<\/code> represents an object SCIM operates on, such as a user or group. SCIM identified core properties each <code class=\"language-plaintext highlighter-rouge\">Resource<\/code> must define, such as <code class=\"language-plaintext highlighter-rouge\">id<\/code> and a link to the resource\u2019s schema definition. From there, a user extends from the core properties and adds attributes specific to the object, such as adding <code class=\"language-plaintext highlighter-rouge\">userName<\/code> and their emails. A standard published schema exists for all those user-specific attributes within the SCIM spec. You can continue extending resources as needed to represent new resources, such as another SCIM standard-defined schema for Enterprise User.<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/user-entitlements-workshop\/resource-class-diagram-696cc8f04feb6e62c81cc68743f76254c52319cfd1c26411d6933db9274a183d.svg\" alt=\"Class diagram representing core Resource properties, User class extending from core Resource adds username and emails properties. The Enterprise User class extends from User adds department and costCenter properties. Group class extends from core Resource and adds displayName and members properties. Other class extends from core Resource demonstrating new resource representations.\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>What\u2019s an example resource other than a user or group? If you said \u201crole\u201d or an \u201centitlement,\u201d you\u2019re correct! Those resource types must have an <code class=\"language-plaintext highlighter-rouge\">id<\/code> and <code class=\"language-plaintext highlighter-rouge\">schemas<\/code>. Here, Okta used SCIM\u2019s extensibility to define a new resource type.<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/user-entitlements-workshop\/resource-role-class-diagram-db8aa8eb1908e9bbbcfe2bae7f02a814ec732e4dc925ae761a629c159839dfd2.svg\" alt=\"Class diagram representing core Resource properties. The User, Group, OktaRole, and Other class extends from core Resource.\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>Okta defines a schema for the <code class=\"language-plaintext highlighter-rouge\">Role<\/code> representation. We can use the schema to ensure we conform to the definition.<\/p>\n\n<h3 id=\"harness-typescript-to-conform-to-scim-schemas\">Harness TypeScript to conform to SCIM schemas<\/h3>\n\n<p>We can define an interface to model the <code class=\"language-plaintext highlighter-rouge\">Role<\/code> representation. Add a new file to the project named <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/scim-types.ts<\/code> and open it up in the IDE. This file will contain the SCIM schema definitions, such as the SCIM core <code class=\"language-plaintext highlighter-rouge\">Resource<\/code>. Each interface defines required and optional properties and the property\u2019s type.<\/p>\n\n<p>Copy and paste the first interface for the SCIM resource into the <code class=\"language-plaintext highlighter-rouge\">scim-types.ts<\/code> file.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kr\">interface<\/span> <span class=\"nx\">IScimResource<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nl\">id<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">[];<\/span>\n  <span class=\"nl\">meta<\/span><span class=\"p\">?:<\/span> <span class=\"nx\">IMetadata<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>A SCIM resource has an optional meta property containing the resource\u2019s metadata. Your IDE shows errors, so we can fix this by adding the <code class=\"language-plaintext highlighter-rouge\">IMetadata<\/code> definition to the file below the <code class=\"language-plaintext highlighter-rouge\">IScimResource<\/code>:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kr\">interface<\/span> <span class=\"nx\">IMetadata<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nl\">resourceType<\/span><span class=\"p\">:<\/span> <span class=\"nx\">RESOURCE_TYPES<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">location<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>You\u2019ll have a new error for <code class=\"language-plaintext highlighter-rouge\">RESOURCE_TYPES<\/code>. We\u2019ll fix it soon.<\/p>\n\n<p>Now, on to the Okta <code class=\"language-plaintext highlighter-rouge\">Role<\/code> representation. The role representation extends from the core SCIM resource and adds extra properties. Okta\u2019s schema overlaps with the SCIM standard User <code class=\"language-plaintext highlighter-rouge\">roles<\/code> field, which includes a property for <code class=\"language-plaintext highlighter-rouge\">display<\/code> text. Define the interface and add it to <code class=\"language-plaintext highlighter-rouge\">IMetadata<\/code> below.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kr\">interface<\/span> <span class=\"nx\">IOktaRole<\/span> <span class=\"kd\">extends<\/span> <span class=\"nx\">IScimResource<\/span><span class=\"p\">{<\/span>\n  <span class=\"nl\">displayName<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">IOktaRole<\/code> extends from the core <code class=\"language-plaintext highlighter-rouge\">IScimResource<\/code> interface and adds a new required property, <code class=\"language-plaintext highlighter-rouge\">displayName<\/code>. Each resource requires a schema, a Uniform Resource Namespace (URN) string. Instead of repeatedly typing the string for each role resource, define it below the <code class=\"language-plaintext highlighter-rouge\">IOktaRole<\/code> interface for reusability<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">SCHEMA_OKTA_ROLE<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">urn:okta:scim:schemas:core:1.0:Role<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Let\u2019s fix the <code class=\"language-plaintext highlighter-rouge\">RESOURCE_TYPES<\/code> error. Below the <code class=\"language-plaintext highlighter-rouge\">SCHEMA_OKTA_ROLE<\/code> constant, add the following:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kd\">type<\/span> <span class=\"nx\">RESOURCE_TYPES<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Role<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>You can use the <code class=\"language-plaintext highlighter-rouge\">IOktaRole<\/code> interface in the <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code> endpoint to ensure the response matches the expected structure. Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/entitlements.ts<\/code>, and update the code to use the interface.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">Router<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">express<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">IOktaRole<\/span><span class=\"p\">,<\/span> <span class=\"nx\">SCHEMA_OKTA_ROLE<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">.\/scim-types<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n\n<span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">rolesRoute<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">Router<\/span><span class=\"p\">();<\/span>\n\n<span class=\"nx\">rolesRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">route<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/<\/span><span class=\"dl\">'<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">.<\/span><span class=\"kd\">get<\/span><span class=\"p\">(<\/span><span class=\"k\">async<\/span> <span class=\"p\">(<\/span><span class=\"nx\">req<\/span><span class=\"p\">,<\/span> <span class=\"nx\">res<\/span><span class=\"p\">)<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IOktaRole<\/span><span class=\"p\">[]<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[{<\/span>\n    <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_OKTA_ROLE<\/span><span class=\"p\">],<\/span>\n    <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">one<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">displayName<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Todo-er<\/span><span class=\"dl\">'<\/span>\n  <span class=\"p\">}];<\/span>\n\n  <span class=\"k\">return<\/span> <span class=\"nx\">res<\/span><span class=\"p\">.<\/span><span class=\"nx\">json<\/span><span class=\"p\">(<\/span><span class=\"nx\">roles<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<blockquote>\n  <p><strong>Why use TypeScript and interfaces?<\/strong><\/p>\n\n  <p>TypeScript, a superset of JavaScript, supports type safety. Type safety means we\u2019ll catch errors within the IDE or at build time instead of getting caught by surprise with a runtime error. Here, we state the <code class=\"language-plaintext highlighter-rouge\">roles<\/code> array is of type <code class=\"language-plaintext highlighter-rouge\">IOktaRole[]<\/code>. Try commenting out the required <code class=\"language-plaintext highlighter-rouge\">schemas<\/code> property. You\u2019ll see an error in an IDE that supports TypeScript or when you try to serve the API as console output. We can use type safety to ensure we meet the expectations of required SCIM properties in our calls.<\/p>\n\n  <p><img src=\"\/assets-jekyll\/blog\/user-entitlements-workshop\/type-error-e1d841636e4957ba250a1e85269ef5807987fd47bd070653fd8289d6737d20d6.jpg\" alt=\"IDE and terminal showing the type error when `schemas` is commented out\" width=\"600\" class=\"center-image\" \/><\/p>\n<\/blockquote>\n\n<p>Every code change deserves a quick check. Serve the API and double check everything still works for you when you make the HTTP call to<\/p>\n\n<div class=\"language-http highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nf\">GET<\/span> <span class=\"nn\">http:\/\/localhost:3333\/scim\/v2\/Roles<\/span> <span class=\"k\">HTTP<\/span><span class=\"o\">\/<\/span><span class=\"m\">1.1<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Do you see the one \u2018Todo-er\u2019 role in the response? \u2705<\/p>\n\n<h3 id=\"scim-list-response\">SCIM list response<\/h3>\n\n<p>We return the array of Okta roles directly in the API response, but this format doesn\u2019t match SCIM list responses. SCIM has a structured response format for lists and a defined schema. This way, SCIM structures all communication between the client and the server so each side knows how to format and parse data.<\/p>\n\n<p>Let\u2019s define the <code class=\"language-plaintext highlighter-rouge\">ListResponse<\/code> interface. Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/scim-types.ts<\/code>. The list response contains standard information supporting pagination, the schema for the list response, and the list of objects. Add the interface to the file. I like to organize my definitions, so I added the code between the <code class=\"language-plaintext highlighter-rouge\">IOktaRole<\/code> interface and <code class=\"language-plaintext highlighter-rouge\">SCHEMA_OKTA_ROLE<\/code> string constant.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kr\">interface<\/span> <span class=\"nx\">IListResponse<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nl\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">[];<\/span>\n  <span class=\"nl\">totalResults<\/span><span class=\"p\">:<\/span> <span class=\"kr\">number<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">startIndex<\/span><span class=\"p\">:<\/span> <span class=\"kr\">number<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">itemsPerPage<\/span><span class=\"p\">:<\/span> <span class=\"kr\">number<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">Resources<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IOktaRole<\/span><span class=\"p\">[];<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The list response also has a schema URN. Create a constant for this string as you did for the Okta role and add it after the role schema string.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">SCHEMA_LIST_RESPONSE<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">urn:ietf:params:scim:api:messages:2.0:ListResponse<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The API response must match the list format. Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/entitlements.ts<\/code> and add <code class=\"language-plaintext highlighter-rouge\">IListResponse<\/code> and <code class=\"language-plaintext highlighter-rouge\">SCHEMA_LIST_RESPONSE<\/code> to the imports from the <code class=\"language-plaintext highlighter-rouge\">scim-types<\/code> file:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">IListResponse<\/span><span class=\"p\">,<\/span> <span class=\"nx\">IOktaRole<\/span><span class=\"p\">,<\/span> <span class=\"nx\">SCHEMA_LIST_RESPONSE<\/span><span class=\"p\">,<\/span> <span class=\"nx\">SCHEMA_OKTA_ROLE<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">.\/scim-types<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Change <code class=\"language-plaintext highlighter-rouge\">rolesRoute<\/code> response to use the list response:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">rolesRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">route<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/<\/span><span class=\"dl\">'<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">.<\/span><span class=\"kd\">get<\/span><span class=\"p\">(<\/span><span class=\"k\">async<\/span> <span class=\"p\">(<\/span><span class=\"nx\">req<\/span><span class=\"p\">,<\/span> <span class=\"nx\">res<\/span><span class=\"p\">)<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IOktaRole<\/span><span class=\"p\">[]<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[{<\/span>\n    <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_OKTA_ROLE<\/span><span class=\"p\">],<\/span>\n    <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">one<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">displayName<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Todo-er<\/span><span class=\"dl\">'<\/span>\n  <span class=\"p\">}];<\/span>\n\n  <span class=\"kd\">const<\/span> <span class=\"na\">listResponse<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IListResponse<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_LIST_RESPONSE<\/span><span class=\"p\">],<\/span>\n    <span class=\"na\">totalResults<\/span><span class=\"p\">:<\/span> <span class=\"nx\">roles<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">itemsPerPage<\/span><span class=\"p\">:<\/span> <span class=\"nx\">roles<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">startIndex<\/span><span class=\"p\">:<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">Resources<\/span><span class=\"p\">:<\/span> <span class=\"nx\">roles<\/span>\n  <span class=\"p\">};<\/span>\n\n  <span class=\"k\">return<\/span> <span class=\"nx\">res<\/span><span class=\"p\">.<\/span><span class=\"nx\">json<\/span><span class=\"p\">(<\/span><span class=\"nx\">listResponse<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Double-check everything still works. Send the HTTP request to your API. \u2705<\/p>\n\n<div class=\"language-http highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nf\">GET<\/span> <span class=\"nn\">http:\/\/localhost:3333\/scim\/v2\/Roles<\/span> <span class=\"k\">HTTP<\/span><span class=\"o\">\/<\/span><span class=\"m\">1.1<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h3 id=\"return-database-defined-roles-in-the-scim-roles-endpoint\">Return database-defined roles in the SCIM <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code> endpoint<\/h3>\n\n<p>Each role has an ID and a name. We can retrieve the roles from the database and populate the <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code> response.<\/p>\n\n<p>Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/entitlements.ts<\/code> and make the changes to retrieve the roles from the database and map the database results to the <code class=\"language-plaintext highlighter-rouge\">IOktaRole<\/code> properties. You\u2019ll need to import some dependencies, so ensure the import statements match. The SCIM <code class=\"language-plaintext highlighter-rouge\">ListResponse<\/code> supports pagination, so we\u2019ll add the required code to consider the query parameters.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">Router<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">express<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">PrismaClient<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">@prisma\/client<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">IListResponse<\/span><span class=\"p\">,<\/span> <span class=\"nx\">IOktaRole<\/span><span class=\"p\">,<\/span> <span class=\"nx\">SCHEMA_LIST_RESPONSE<\/span><span class=\"p\">,<\/span> <span class=\"nx\">SCHEMA_OKTA_ROLE<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">.\/scim-types<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n\n<span class=\"kd\">const<\/span> <span class=\"nx\">prisma<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nx\">PrismaClient<\/span><span class=\"p\">();<\/span>\n\n<span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">rolesRoute<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">Router<\/span><span class=\"p\">();<\/span>\n\n<span class=\"nx\">rolesRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">route<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/<\/span><span class=\"dl\">'<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">.<\/span><span class=\"kd\">get<\/span><span class=\"p\">(<\/span><span class=\"k\">async<\/span> <span class=\"p\">(<\/span><span class=\"nx\">req<\/span><span class=\"p\">,<\/span> <span class=\"nx\">res<\/span><span class=\"p\">)<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"nx\">startIndex<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">parseInt<\/span><span class=\"p\">(<\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">query<\/span><span class=\"p\">.<\/span><span class=\"nx\">startIndex<\/span> <span class=\"k\">as<\/span> <span class=\"kr\">string<\/span> <span class=\"o\">??<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">1<\/span><span class=\"dl\">'<\/span><span class=\"p\">);<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"nx\">recordLimit<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">parseInt<\/span><span class=\"p\">(<\/span><span class=\"nx\">req<\/span><span class=\"p\">.<\/span><span class=\"nx\">query<\/span><span class=\"p\">.<\/span><span class=\"nx\">recordLimit<\/span> <span class=\"k\">as<\/span> <span class=\"kr\">string<\/span> <span class=\"o\">??<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">100<\/span><span class=\"dl\">'<\/span><span class=\"p\">);<\/span>\n\n  <span class=\"kd\">const<\/span> <span class=\"nx\">roles<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">findMany<\/span><span class=\"p\">({<\/span>\n    <span class=\"na\">take<\/span><span class=\"p\">:<\/span> <span class=\"nx\">recordLimit<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">skip<\/span><span class=\"p\">:<\/span> <span class=\"nx\">startIndex<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span>\n  <span class=\"p\">});<\/span>\n\n<span class=\"kd\">const<\/span> <span class=\"na\">listResponse<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IListResponse<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_LIST_RESPONSE<\/span><span class=\"p\">],<\/span>\n    <span class=\"na\">totalResults<\/span><span class=\"p\">:<\/span> <span class=\"nx\">roles<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">startIndex<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">itemsPerPage<\/span><span class=\"p\">:<\/span> <span class=\"nx\">recordLimit<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">Resources<\/span><span class=\"p\">:<\/span> <span class=\"nx\">roles<\/span><span class=\"p\">.<\/span><span class=\"nx\">map<\/span><span class=\"p\">(<\/span><span class=\"nx\">role<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">({<\/span>\n      <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_OKTA_ROLE<\/span><span class=\"p\">],<\/span>\n      <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">id<\/span><span class=\"p\">.<\/span><span class=\"nx\">toString<\/span><span class=\"p\">(),<\/span>\n      <span class=\"na\">displayName<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">name<\/span>\n    <span class=\"p\">}))<\/span>\n  <span class=\"p\">};<\/span>\n\n  <span class=\"k\">return<\/span> <span class=\"nx\">res<\/span><span class=\"p\">.<\/span><span class=\"nx\">json<\/span><span class=\"p\">(<\/span><span class=\"nx\">listResponse<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Run a quick check to ensure everything still works. Serve the API and call the <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code> endpoint using your HTTP client. \u2705<\/p>\n\n<p>You should see three roles matching the roles in the database. \ud83c\udf89<\/p>\n\n<h2 id=\"scim-resource-types\">SCIM resource types<\/h2>\n\n<p>We implemented the <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code> endpoint and discussed how SCIM defines a resource. But how would the SCIM client know about this Okta Role type? Enter discovery\u2014learning about a SCIM server\u2019s capabilities and supported objects such as resources!<\/p>\n\n<p>SCIM clients and servers communicate about the types of resources through a standard endpoint, the<code class=\"language-plaintext highlighter-rouge\">\/ResourceType<\/code> endpoint. SCIM clients call the endpoint to discover what resources they can expect. The endpoint returns a SCIM list response outlining resources. You can add every resource type used, including the standard <code class=\"language-plaintext highlighter-rouge\">User<\/code> and <code class=\"language-plaintext highlighter-rouge\">EnterpriseUser<\/code> resources, but Okta expects resource definitions only for custom types.<\/p>\n\n<p>First, we\u2019ll create the interface for the <code class=\"language-plaintext highlighter-rouge\">ResourceType<\/code> and define some strings. Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/scim-types.ts<\/code>. Add the interface for <code class=\"language-plaintext highlighter-rouge\">IResourceType<\/code> above the <code class=\"language-plaintext highlighter-rouge\">IListResponse<\/code> interface.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kr\">interface<\/span> <span class=\"nx\">IResourceType<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nl\">id<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">[];<\/span>\n  <span class=\"nl\">name<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span> \n  <span class=\"nl\">description<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">endpoint<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">schema<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span> \n  <span class=\"nl\">meta<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IMetadata<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Notice the <code class=\"language-plaintext highlighter-rouge\">IResourceType<\/code> doesn\u2019t extend from the <code class=\"language-plaintext highlighter-rouge\">IScimResource<\/code> interface. For example, the SCIM standard doesn\u2019t require <code class=\"language-plaintext highlighter-rouge\">id<\/code> for a resource type. Since the SCIM standard treats <code class=\"language-plaintext highlighter-rouge\">ResourceType<\/code> as an exception case of <code class=\"language-plaintext highlighter-rouge\">Resource<\/code>, we defined it separately without the relation instead of extending from <code class=\"language-plaintext highlighter-rouge\">IScimResource<\/code>.<\/p>\n\n<p>When following the SCIM protocol, responses that list values, such as the list of roles or resource types, use the SCIM list response format.<\/p>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">IListResource<\/code> interface must support <code class=\"language-plaintext highlighter-rouge\">IOktaRole<\/code> and <code class=\"language-plaintext highlighter-rouge\">IResourceType<\/code>. Using <a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/2\/generics.html\">generics<\/a> and <a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/2\/everyday-types.html#union-types\">union types<\/a>, we can support different list response objects . Update the <code class=\"language-plaintext highlighter-rouge\">IListResource<\/code> to match the code below.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kr\">interface<\/span> <span class=\"nx\">IListResponse<\/span><span class=\"o\">&lt;<\/span><span class=\"nx\">T<\/span> <span class=\"kd\">extends<\/span> <span class=\"nx\">IScimResource<\/span> <span class=\"o\">|<\/span> <span class=\"nx\">IResourceType<\/span><span class=\"o\">&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">[];<\/span>\n  <span class=\"nl\">totalResults<\/span><span class=\"p\">:<\/span> <span class=\"kr\">number<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">startIndex<\/span><span class=\"p\">:<\/span> <span class=\"kr\">number<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">itemsPerPage<\/span><span class=\"p\">:<\/span> <span class=\"kr\">number<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">Resources<\/span><span class=\"p\">:<\/span> <span class=\"nx\">T<\/span><span class=\"p\">[];<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>You\u2019ll see errors in the IDE and, if you\u2019re running the API, within the console output. No worries; we\u2019ll fix those errors soon!<\/p>\n\n<p>Resource types have a schema URN and use \u201cResourceType\u201d as the <code class=\"language-plaintext highlighter-rouge\">resourceType<\/code> string in the metadata. Add <code class=\"language-plaintext highlighter-rouge\">SCHEMA_RESOURCE_TYPE<\/code> and edit <code class=\"language-plaintext highlighter-rouge\">RESOURCE_TYPES<\/code> so your string constants section looks like the code below.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">SCHEMA_OKTA_ROLE<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">urn:okta:scim:schemas:core:1.0:Role<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">SCHEMA_LIST_RESPONSE<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">urn:ietf:params:scim:api:messages:2.0:ListResponse<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">SCHEMA_RESOURCE_TYPE<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">urn:ietf:params:scim:schemas:core:2.0:ResourceType<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">export<\/span> <span class=\"kd\">type<\/span> <span class=\"nx\">RESOURCE_TYPES<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Role<\/span><span class=\"dl\">'<\/span> <span class=\"o\">|<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">ResourceType<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/entitlements.ts<\/code>. Let\u2019s fix the <code class=\"language-plaintext highlighter-rouge\">IListResponse<\/code> error for the <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code> endpoint and specify the object type in the list, the <code class=\"language-plaintext highlighter-rouge\">IOktaRole<\/code> type. The code building out the list changes to<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>  <span class=\"kd\">const<\/span> <span class=\"nx\">listResponse<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IListResponse<\/span><span class=\"o\">&lt;<\/span><span class=\"nx\">IOktaRole<\/span><span class=\"o\">&gt;<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_LIST_RESPONSE<\/span><span class=\"p\">],<\/span>\n    <span class=\"na\">totalResults<\/span><span class=\"p\">:<\/span> <span class=\"nx\">roles<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">startIndex<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">itemsPerPage<\/span><span class=\"p\">:<\/span> <span class=\"nx\">recordLimit<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">Resources<\/span><span class=\"p\">:<\/span> <span class=\"nx\">roles<\/span><span class=\"p\">.<\/span><span class=\"nx\">map<\/span><span class=\"p\">(<\/span><span class=\"nx\">role<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">({<\/span>\n      <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_OKTA_ROLE<\/span><span class=\"p\">],<\/span>\n      <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">id<\/span><span class=\"p\">.<\/span><span class=\"nx\">toString<\/span><span class=\"p\">(),<\/span>\n      <span class=\"na\">displayName<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">name<\/span>\n    <span class=\"p\">}))<\/span>\n  <span class=\"p\">};<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>You shouldn\u2019t see errors anymore! \ud83c\udf89<\/p>\n\n<p>We have a new endpoint to add. Update the imports from the <code class=\"language-plaintext highlighter-rouge\">.\/scim-types<\/code> file and declare a new route for resource types.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">Router<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">express<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">PrismaClient<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">@prisma\/client<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">import<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">IListResponse<\/span><span class=\"p\">,<\/span> <span class=\"nx\">IOktaRole<\/span><span class=\"p\">,<\/span> <span class=\"nx\">IResourceType<\/span><span class=\"p\">,<\/span> <span class=\"nx\">SCHEMA_LIST_RESPONSE<\/span><span class=\"p\">,<\/span> <span class=\"nx\">SCHEMA_OKTA_ROLE<\/span><span class=\"p\">,<\/span> <span class=\"nx\">SCHEMA_RESOURCE_TYPE<\/span>\n<span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">.\/scim-types<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n\n<span class=\"kd\">const<\/span> <span class=\"nx\">prisma<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nx\">PrismaClient<\/span><span class=\"p\">();<\/span>\n<span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">rolesRoute<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">Router<\/span><span class=\"p\">();<\/span>\n<span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">resourceTypesRoute<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">Router<\/span><span class=\"p\">();<\/span>\n\n\n<span class=\"c1\">\/\/ existing rolesRoute code below<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Then create the <code class=\"language-plaintext highlighter-rouge\">\/ResourceTypes<\/code> route by adding the code below the <code class=\"language-plaintext highlighter-rouge\">rolesRoute<\/code><\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">resourceTypesRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">route<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/<\/span><span class=\"dl\">'<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">.<\/span><span class=\"kd\">get<\/span><span class=\"p\">((<\/span><span class=\"nx\">req<\/span><span class=\"p\">,<\/span> <span class=\"nx\">res<\/span><span class=\"p\">)<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"na\">resourceTypes<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IResourceType<\/span><span class=\"p\">[]<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[{<\/span>\n    <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_RESOURCE_TYPE<\/span><span class=\"p\">],<\/span>\n    <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Role<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Role<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">endpoint<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">\/Roles<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">description<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Roles you can set on users of Todo App<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">schema<\/span><span class=\"p\">:<\/span> <span class=\"nx\">SCHEMA_OKTA_ROLE<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">meta<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">resourceType<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">ResourceType<\/span><span class=\"dl\">'<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">}];<\/span>\n\n  <span class=\"kd\">const<\/span> <span class=\"na\">resourceTypesListResponse<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IListResponse<\/span><span class=\"o\">&lt;<\/span><span class=\"nx\">IResourceType<\/span><span class=\"o\">&gt;<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_LIST_RESPONSE<\/span><span class=\"p\">],<\/span>\n    <span class=\"na\">totalResults<\/span><span class=\"p\">:<\/span> <span class=\"nx\">resourceTypes<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">startIndex<\/span><span class=\"p\">:<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">itemsPerPage<\/span><span class=\"p\">:<\/span> <span class=\"nx\">resourceTypes<\/span><span class=\"p\">.<\/span><span class=\"nx\">length<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">Resources<\/span><span class=\"p\">:<\/span> <span class=\"nx\">resourceTypes<\/span>\n  <span class=\"p\">};<\/span>\n\n  <span class=\"k\">return<\/span> <span class=\"nx\">res<\/span><span class=\"p\">.<\/span><span class=\"nx\">json<\/span><span class=\"p\">(<\/span><span class=\"nx\">resourceTypesListResponse<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Next, you must register the <code class=\"language-plaintext highlighter-rouge\">\/ResourceTypes<\/code> route in the API. Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/scim.ts<\/code>.<\/p>\n\n<p>Update the import to include <code class=\"language-plaintext highlighter-rouge\">resourceTypesRoute<\/code><\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">resourceTypesRoute<\/span><span class=\"p\">,<\/span> <span class=\"nx\">rolesRoute<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">.\/entitlements<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Add the <code class=\"language-plaintext highlighter-rouge\">\/ResourceTypes<\/code> endpoint to the end of the file. You should have two routes defined.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">scimRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">use<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/Roles<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"nx\">rolesRoute<\/span> <span class=\"p\">);<\/span>\n<span class=\"nx\">scimRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">use<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/ResourceTypes<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"nx\">resourceTypesRoute<\/span><span class=\"p\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Double-check your new route by starting the API if it\u2019s not running. Use your HTTP client to make the call<\/p>\n\n<div class=\"language-http highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nf\">GET<\/span> <span class=\"nn\">http:\/\/localhost:3333\/scim\/v2\/ResourceTypes<\/span> <span class=\"k\">HTTP<\/span><span class=\"o\">\/<\/span><span class=\"m\">1.1<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>If you see a response with the Okta role resource type, the API call works as expected! \u2705<\/p>\n\n<h2 id=\"add-roles-to-the-scim-users-endpoints\">Add roles to the SCIM Users endpoints<\/h2>\n\n<p>Let\u2019s add roles to the existing user calls. We want to reflect a user\u2019s roles in Okta within the Todo app, so the GET and POST <code class=\"language-plaintext highlighter-rouge\">\/Users<\/code> calls must support roles. Near the top of the <code class=\"language-plaintext highlighter-rouge\">scim.ts<\/code> file, find <code class=\"language-plaintext highlighter-rouge\">IUserSchema<\/code> interface.<\/p>\n\n<p>Update the interface to add the <code class=\"language-plaintext highlighter-rouge\">roles<\/code> property:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kr\">interface<\/span> <span class=\"nx\">IUserSchema<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nl\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">[];<\/span>\n  <span class=\"nl\">userName<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">id<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">name<\/span><span class=\"p\">?:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">givenName<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n    <span class=\"nl\">familyName<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"p\">};<\/span>\n  <span class=\"nl\">emails<\/span><span class=\"p\">?:<\/span> <span class=\"p\">{<\/span><span class=\"na\">primary<\/span><span class=\"p\">:<\/span> <span class=\"nx\">boolean<\/span><span class=\"p\">,<\/span> <span class=\"na\">value<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">,<\/span> <span class=\"na\">type<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">}[];<\/span>\n  <span class=\"nl\">displayName<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">locale<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">meta<\/span><span class=\"p\">?:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">resourceType<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"p\">}<\/span>\n  <span class=\"nl\">externalId<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">groups<\/span><span class=\"p\">?:<\/span> <span class=\"p\">[];<\/span>\n  <span class=\"nl\">password<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">active<\/span><span class=\"p\">?:<\/span> <span class=\"nx\">boolean<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">detail<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">status<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">number<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">roles<\/span><span class=\"p\">?:<\/span> <span class=\"p\">{<\/span><span class=\"na\">value<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">,<\/span> <span class=\"na\">display<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">}[];<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The User SCIM schema defines <code class=\"language-plaintext highlighter-rouge\">roles<\/code> property as a list of objects that may contain properties named <code class=\"language-plaintext highlighter-rouge\">value<\/code> and <code class=\"language-plaintext highlighter-rouge\">display<\/code>, among others. Okta uses these properties for role data.<\/p>\n\n<h3 id=\"update-the-scim-add-users-call-to-include-roles\">Update the SCIM add users call to include roles<\/h3>\n\n<p>The first route defined is the <code class=\"language-plaintext highlighter-rouge\">POST \/Users<\/code> route definition. You need to add roles when saving to the database. Find the comment<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ Create the User in the database<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>and update the database command and the as shown.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ Create the User in the database<\/span>\n<span class=\"kd\">const<\/span> <span class=\"nx\">user<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">create<\/span><span class=\"p\">({<\/span>\n  <span class=\"na\">data<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">org<\/span> <span class=\"p\">:<\/span> <span class=\"p\">{<\/span> <span class=\"na\">connect<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span><span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"nx\">ORG_ID<\/span><span class=\"p\">}},<\/span>\n    <span class=\"nx\">name<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">email<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">password<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">externalId<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">active<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">connect<\/span><span class=\"p\">:<\/span> <span class=\"nx\">newUser<\/span><span class=\"p\">.<\/span><span class=\"nx\">roles<\/span><span class=\"p\">?.<\/span><span class=\"nx\">map<\/span><span class=\"p\">(<\/span><span class=\"nx\">role<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">({<\/span><span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"nb\">parseInt<\/span><span class=\"p\">(<\/span><span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">value<\/span><span class=\"p\">)}))<\/span> <span class=\"o\">||<\/span> <span class=\"p\">[]<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">},<\/span>\n  <span class=\"na\">include<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">});<\/span>\n\n<span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nx\">log<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">Account Created ID: <\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">id<\/span><span class=\"p\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>One more place to update in the <code class=\"language-plaintext highlighter-rouge\">POST \/Users<\/code> call. We need to return the roles in the response. Right below the <code class=\"language-plaintext highlighter-rouge\">console.log()<\/code> update the <code class=\"language-plaintext highlighter-rouge\">userResponse<\/code> to<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">userResponse<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span> <span class=\"p\">...<\/span><span class=\"nx\">defaultUserSchema<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"s2\">`<\/span><span class=\"p\">${<\/span><span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">id<\/span><span class=\"p\">}<\/span><span class=\"s2\">`<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">userName<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">email<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nx\">givenName<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">familyName<\/span>\n  <span class=\"p\">},<\/span>\n  <span class=\"na\">emails<\/span><span class=\"p\">:<\/span> <span class=\"p\">[{<\/span>\n    <span class=\"na\">primary<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">value<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">email<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">type<\/span><span class=\"p\">:<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">work<\/span><span class=\"dl\">\"<\/span>\n  <span class=\"p\">}],<\/span>\n  <span class=\"na\">displayName<\/span><span class=\"p\">:<\/span> <span class=\"nx\">name<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">externalId<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">externalId<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">active<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">active<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">roles<\/span><span class=\"p\">.<\/span><span class=\"nx\">map<\/span><span class=\"p\">(<\/span><span class=\"nx\">role<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">({<\/span><span class=\"na\">display<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">name<\/span><span class=\"p\">,<\/span> <span class=\"na\">value<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">id<\/span><span class=\"p\">.<\/span><span class=\"nx\">toString<\/span><span class=\"p\">()}))<\/span>\n<span class=\"p\">};<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h3 id=\"add-roles-when-getting-a-list-of-users-in-scim\">Add roles when getting a list of users in SCIM<\/h3>\n\n<p>Continuing to the <code class=\"language-plaintext highlighter-rouge\">GET \/Users<\/code> call, search for the code to find users in the database<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">findMany<\/span><span class=\"p\">({...});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>to add <code class=\"language-plaintext highlighter-rouge\">roles<\/code> to the <code class=\"language-plaintext highlighter-rouge\">select<\/code> argument.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">const<\/span> <span class=\"nx\">users<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">findMany<\/span><span class=\"p\">({<\/span>\n  <span class=\"na\">take<\/span><span class=\"p\">:<\/span> <span class=\"nx\">recordLimit<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">skip<\/span><span class=\"p\">:<\/span> <span class=\"nx\">startIndex<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">select<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">email<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">externalId<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">active<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span>\n  <span class=\"p\">},<\/span>\n  <span class=\"nx\">where<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">GET \/Users<\/code> response also needs roles, so update the<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">usersResponse<\/span><span class=\"p\">[<\/span><span class=\"dl\">'<\/span><span class=\"s1\">Resources<\/span><span class=\"dl\">'<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">users<\/span><span class=\"p\">.<\/span><span class=\"nx\">map<\/span><span class=\"p\">(<\/span><span class=\"nx\">user<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{...});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>like this.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">usersResponse<\/span><span class=\"p\">[<\/span><span class=\"dl\">'<\/span><span class=\"s1\">Resources<\/span><span class=\"dl\">'<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">users<\/span><span class=\"p\">.<\/span><span class=\"nx\">map<\/span><span class=\"p\">(<\/span><span class=\"nx\">user<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"p\">[<\/span><span class=\"nx\">givenName<\/span><span class=\"p\">,<\/span> <span class=\"nx\">familyName<\/span><span class=\"p\">]<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">name<\/span><span class=\"p\">.<\/span><span class=\"nx\">split<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\"> <\/span><span class=\"dl\">\"<\/span><span class=\"p\">);<\/span>\n  <span class=\"k\">return<\/span> <span class=\"p\">{<\/span>\n    <span class=\"p\">...<\/span><span class=\"nx\">defaultUserSchema<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">id<\/span><span class=\"p\">.<\/span><span class=\"nx\">toString<\/span><span class=\"p\">(),<\/span>\n    <span class=\"na\">userName<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">email<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n      <span class=\"nx\">givenName<\/span><span class=\"p\">,<\/span>\n      <span class=\"nx\">familyName<\/span>\n    <span class=\"p\">},<\/span>\n    <span class=\"na\">emails<\/span><span class=\"p\">:<\/span> <span class=\"p\">[{<\/span>\n      <span class=\"na\">primary<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">value<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">email<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">type<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">work<\/span><span class=\"dl\">'<\/span>\n    <span class=\"p\">}],<\/span>\n    <span class=\"na\">displayName<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">name<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">externalId<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">externalId<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">active<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">active<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">roles<\/span><span class=\"p\">.<\/span><span class=\"nx\">map<\/span><span class=\"p\">(<\/span><span class=\"nx\">role<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">({<\/span><span class=\"na\">display<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">name<\/span><span class=\"p\">,<\/span> <span class=\"na\">value<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">id<\/span><span class=\"p\">.<\/span><span class=\"nx\">toString<\/span><span class=\"p\">()}))<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h4 id=\"update-the-response-for-an-individual-user\">Update the response for an individual user<\/h4>\n\n<p>On to the next call, <code class=\"language-plaintext highlighter-rouge\">GET \/Users\/:userId<\/code>. We need to add <code class=\"language-plaintext highlighter-rouge\">roles<\/code> to the<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">const<\/span> <span class=\"nx\">user<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">findFirst<\/span><span class=\"p\">({...});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>database command. Update it to match the code below.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">const<\/span> <span class=\"nx\">user<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">findFirst<\/span><span class=\"p\">({<\/span>\n  <span class=\"na\">select<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">email<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">externalId<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">active<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span>\n  <span class=\"p\">},<\/span>\n  <span class=\"na\">where<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nx\">id<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">org<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span><span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"nx\">ORG_ID<\/span><span class=\"p\">},<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Then, find the comment<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ If no response from DB, return 404<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>to update the <code class=\"language-plaintext highlighter-rouge\">userResponse<\/code> object inside the <code class=\"language-plaintext highlighter-rouge\">if<\/code> statement. Update the <code class=\"language-plaintext highlighter-rouge\">userResponse<\/code> to match the code shown.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">userResponse<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n  <span class=\"p\">...<\/span><span class=\"nx\">defaultUserSchema<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"nx\">id<\/span><span class=\"p\">.<\/span><span class=\"nx\">toString<\/span><span class=\"p\">(),<\/span>\n  <span class=\"na\">userName<\/span><span class=\"p\">:<\/span> <span class=\"nx\">email<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nx\">givenName<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">familyName<\/span>\n  <span class=\"p\">},<\/span>\n  <span class=\"na\">emails<\/span><span class=\"p\">:<\/span> <span class=\"p\">[{<\/span>\n    <span class=\"na\">primary<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">value<\/span><span class=\"p\">:<\/span> <span class=\"nx\">email<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">type<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">work<\/span><span class=\"dl\">'<\/span>\n  <span class=\"p\">}],<\/span>\n  <span class=\"na\">displayName<\/span><span class=\"p\">:<\/span> <span class=\"nx\">name<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">externalId<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">externalId<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">active<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">active<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">roles<\/span><span class=\"p\">.<\/span><span class=\"nx\">map<\/span><span class=\"p\">(<\/span><span class=\"nx\">role<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">({<\/span><span class=\"na\">display<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">name<\/span><span class=\"p\">,<\/span> <span class=\"na\">value<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">id<\/span><span class=\"p\">.<\/span><span class=\"nx\">toString<\/span><span class=\"p\">()}))<\/span>\n<span class=\"p\">}<\/span> <span class=\"nx\">satisfies<\/span> <span class=\"nx\">IUserSchema<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h3 id=\"update-the-users-call-so-scim-clients-can-set-their-roles\">Update the <code class=\"language-plaintext highlighter-rouge\">\/Users<\/code> call so SCIM clients can set their roles<\/h3>\n\n<p>Another endpoint down, but there\u2019s one more left, the <code class=\"language-plaintext highlighter-rouge\">PUT \/Users\/:userId<\/code>.<\/p>\n\n<p>Find the code<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">const<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">name<\/span><span class=\"p\">,<\/span> <span class=\"nx\">emails<\/span> <span class=\"p\">}<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">updatedUserRequest<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>and change it to the following code so we can work with the user\u2019s updated roles and save the changes in the database.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">const<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">name<\/span><span class=\"p\">,<\/span> <span class=\"nx\">emails<\/span><span class=\"p\">,<\/span> <span class=\"nx\">roles<\/span> <span class=\"p\">}<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">updatedUserRequest<\/span><span class=\"p\">;<\/span>\n\n<span class=\"kd\">const<\/span> <span class=\"nx\">updatedUser<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"nx\">prisma<\/span><span class=\"p\">.<\/span><span class=\"nx\">user<\/span><span class=\"p\">.<\/span><span class=\"nx\">update<\/span><span class=\"p\">({<\/span>\n  <span class=\"na\">data<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">email<\/span><span class=\"p\">:<\/span> <span class=\"nx\">emails<\/span><span class=\"p\">.<\/span><span class=\"nx\">find<\/span><span class=\"p\">(<\/span><span class=\"nx\">email<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"nx\">email<\/span><span class=\"p\">.<\/span><span class=\"nx\">primary<\/span><span class=\"p\">).<\/span><span class=\"nx\">value<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"s2\">`<\/span><span class=\"p\">${<\/span><span class=\"nx\">name<\/span><span class=\"p\">.<\/span><span class=\"nx\">givenName<\/span><span class=\"p\">}<\/span><span class=\"s2\"> <\/span><span class=\"p\">${<\/span><span class=\"nx\">name<\/span><span class=\"p\">.<\/span><span class=\"nx\">familyName<\/span><span class=\"p\">}<\/span><span class=\"s2\">`<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">set<\/span><span class=\"p\">:<\/span> <span class=\"nx\">roles<\/span><span class=\"p\">?.<\/span><span class=\"nx\">map<\/span><span class=\"p\">(<\/span><span class=\"nx\">role<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">({<\/span><span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"nb\">parseInt<\/span><span class=\"p\">(<\/span><span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">value<\/span><span class=\"p\">)}))<\/span> <span class=\"o\">||<\/span> <span class=\"p\">[]<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">},<\/span>\n  <span class=\"na\">where<\/span> <span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nx\">id<\/span>\n  <span class=\"p\">},<\/span>\n  <span class=\"na\">include<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Lastly, we need to update the response from the <code class=\"language-plaintext highlighter-rouge\">PUT \/Users\/:userId<\/code> call. Update the <code class=\"language-plaintext highlighter-rouge\">userResponse<\/code> object to look like this.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">userResponse<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n  <span class=\"p\">...<\/span><span class=\"nx\">defaultUserSchema<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"nx\">id<\/span><span class=\"p\">.<\/span><span class=\"nx\">toString<\/span><span class=\"p\">(),<\/span>\n  <span class=\"na\">userName<\/span><span class=\"p\">:<\/span> <span class=\"nx\">updatedUser<\/span><span class=\"p\">.<\/span><span class=\"nx\">email<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nx\">givenName<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">familyName<\/span>\n  <span class=\"p\">},<\/span>\n  <span class=\"na\">emails<\/span><span class=\"p\">:<\/span> <span class=\"p\">[{<\/span>\n    <span class=\"na\">primary<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">value<\/span><span class=\"p\">:<\/span> <span class=\"nx\">updatedUser<\/span><span class=\"p\">.<\/span><span class=\"nx\">email<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">type<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">work<\/span><span class=\"dl\">'<\/span>\n  <span class=\"p\">}],<\/span>\n  <span class=\"na\">displayName<\/span><span class=\"p\">:<\/span> <span class=\"nx\">updatedUser<\/span><span class=\"p\">.<\/span><span class=\"nx\">name<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">externalId<\/span><span class=\"p\">:<\/span> <span class=\"nx\">updatedUser<\/span><span class=\"p\">.<\/span><span class=\"nx\">externalId<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">active<\/span><span class=\"p\">:<\/span> <span class=\"nx\">updatedUser<\/span><span class=\"p\">.<\/span><span class=\"nx\">active<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">roles<\/span><span class=\"p\">:<\/span> <span class=\"nx\">updatedUser<\/span><span class=\"p\">.<\/span><span class=\"nx\">roles<\/span><span class=\"p\">?.<\/span><span class=\"nx\">map<\/span><span class=\"p\">(<\/span><span class=\"nx\">role<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">({<\/span><span class=\"na\">display<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">name<\/span><span class=\"p\">,<\/span> <span class=\"na\">value<\/span><span class=\"p\">:<\/span> <span class=\"nx\">role<\/span><span class=\"p\">.<\/span><span class=\"nx\">id<\/span><span class=\"p\">.<\/span><span class=\"nx\">toString<\/span><span class=\"p\">()}))<\/span>\n<span class=\"p\">}<\/span> <span class=\"nx\">satisfies<\/span> <span class=\"nx\">IUserSchema<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Serve the API if you aren\u2019t running it using <code class=\"language-plaintext highlighter-rouge\">npm run serve-api<\/code>. Let\u2019s make an HTTP call to get all users to double-check our work.<\/p>\n\n<div class=\"language-http highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"err\">GET http:\/\/localhost:3333\/scim\/v2\/Users\nAuthorization: Bearer 123123\n<\/span><\/code><\/pre><\/div><\/div>\n\n<p>You will see the list of users. Each user object has a <code class=\"language-plaintext highlighter-rouge\">roles<\/code> and <code class=\"language-plaintext highlighter-rouge\">entitlements<\/code> property. \u2705<\/p>\n\n<h2 id=\"entitlements-discovery-in-okta\">Entitlements discovery in Okta<\/h2>\n\n<p>What can Okta do with user entitlements? Okta can discover defined entitlements, such as the roles you define for the Todo app, and applies existing roles on users. Now that you have all the endpoints needed for a SCIM client to discover resources held by a SCIM server, you can see this in action on Okta.<\/p>\n\n<p>You\u2019ll need to serve the API and create a local tunnel. Serve the API using the <code class=\"language-plaintext highlighter-rouge\">npm run serve-api<\/code> command. In a second terminal window, run <code class=\"language-plaintext highlighter-rouge\">npx localtunnel --port 3333<\/code>. Take note of your tunnel URL.<\/p>\n\n<p>Sign into your <a href=\"https:\/\/developer.okta.com\/login\/\">Okta Developer Edition account<\/a>. Navigate to <strong>Applications<\/strong> &gt; <strong>Applications<\/strong> and select the \u201c(Header Auth) Governance with SCIM 2.0\u201d app. Navigate to the <strong>Provisioning<\/strong> tab and select <strong>Integration<\/strong>. Press <strong>Edit<\/strong>.<\/p>\n\n<p>Update the <strong>Base URL<\/strong> field by replacing the tunnel URL with your new tunnel URL. Make sure you keep the <code class=\"language-plaintext highlighter-rouge\">\/scim\/v2<\/code> path. Your base URL might look something like <code class=\"language-plaintext highlighter-rouge\">https:\/\/beep-bop-boop.loca.lt\/scim\/v2<\/code>. Press <strong>Save<\/strong>.<\/p>\n\n<p>Updating the API integration kicks off a discovery process. Okta automatically looks for roles as a possible entitlement type. It then matches the roles it discovers for the Todo application and matches them again with roles defined on the users. You can see Okta working by looking at the terminal window serving the API. You can see the calls Okta makes by inspecting the HTTP requests and their payloads written to the console.  \ud83d\udd0d<\/p>\n\n<p>Make sure to keep the API running! There\u2019s more work to do here!<\/p>\n\n<p>Navigate to the <strong>Governance<\/strong> tab. The tab you see is <strong>Entitlements<\/strong>. Do you see <strong>Role<\/strong> in the sidenav below the <strong>Search<\/strong> input? If not, hang tight. Because an app may have many defined entitlements, Okta starts a background job to discover roles asynchronously. It could take up to 10 minutes for the roles to populate.<\/p>\n\n<p>Eventually, you\u2019ll see <strong>Role<\/strong>; when you select it, you\u2019ll see metadata about it, such as the variable name, data type, and description. We also see the values: \u201cManager,\u201d \u201cTodo Auditor,\u201d and \u201cTodo-er.\u201d<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/user-entitlements-workshop\/entitlements-roles-7344da8e7387c37c7b6f3c9d16abcbf326e6477fe02ebb172b2eaf2666ee2958.jpg\" alt=\"Governance tab with roles discovered by Okta\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>You can define policies for users that automatically assign their entitlements when adding them to this integration app. While that\u2019s pretty nifty, this post focuses on building out the SCIM endpoints for entitlements, so I\u2019ll include links to resources that explain this feature in more detail at the end of the post.<\/p>\n\n<p>Press <strong>&lt; Back to application<\/strong> to return to the SCIM Okta app.<\/p>\n\n<h3 id=\"syncing-user-entitlements\">Syncing user entitlements<\/h3>\n\n<p>When you use an identity provider, you want that system to be the source of truth for managing the users\u2019 identities and access levels. You want to set the roles you defined for the Todo app onto users within Okta. That would be pretty sweet, right?<\/p>\n\n<p>Since we last ran our user import with hardcoded roles, let\u2019s ensure we\u2019ve synchronized everything from the starting state of the application before we start managing with Okta.<\/p>\n\n<p>Within the SCIM application tab, navigate to <strong>Import<\/strong> and press the <strong>Import Now<\/strong> button. Okta scans the users in the todo app, but since there are no new users, there\u2019s no confirmation process. The user scan synced the existing users and the roles!<\/p>\n\n<p>Navigate to <strong>Assignments<\/strong>. Each user has a vertical 3-dot menu icon to display a context menu allowing you to <strong>Edit user assignment<\/strong>, <strong>View access details **, and **Unassign<\/strong>. Find \u201cTrinity\u201d and **View access details ** on them. A panel shows you Trinity\u2019s role pre-assigned in the Todo app. \ud83c\udf89 Exit the side panel by clicking outside the side panel.<\/p>\n\n<p>Let\u2019s assign a new role to \u201cSomnus\u201d using Okta.  Open the context menu for \u201cSomnus\u201d and <strong>View access details<\/strong>. Press the <strong>Edit access<\/strong> button. You\u2019ll see a page titled <strong>Edit access<\/strong>. Press the <strong>Customize entitlements<\/strong> button. You\u2019ll see a warning followed by a section called <strong>Custom Entitlements<\/strong>.<\/p>\n\n<p>You\u2019ll see <strong>Role<\/strong> and a dropdown list with values. Select a role, such as \u201cTodo-er,\u201d and press <strong>Save<\/strong> to add the role to the user.<\/p>\n\n<p>But how about the Todo app? Take a look at the terminal output where you\u2019re serving the API. The HTTP call tracing shows a <code class=\"language-plaintext highlighter-rouge\">PUT<\/code> request on the user adding the role. Can you see the role of the user in the database? You can check it out by opening another terminal window, running <code class=\"language-plaintext highlighter-rouge\">npx prisma studio<\/code>, and navigating to the website. \u2705<\/p>\n\n<p>You can now use Okta to manage user roles centrally and automatically update the user\u2019s grants!<\/p>\n\n<p>Stop serving the local tunnel and API for this next section.<\/p>\n\n<h3 id=\"schema-discovery-for-custom-entitlements\">Schema discovery for custom entitlements<\/h3>\n\n<p>What if we have something other than roles in the application? Can SCIM support custom entitlement strategies? SCIM is extensible, meaning it has the structure for custom schemas and extends beyond the core resources. A SCIM server can publish a custom schema if it defines custom resource types.<\/p>\n\n<p>Let\u2019s say you have user roles but want to add a custom entitlement, such as licenses, profiles, or something else. Let\u2019s walk through the example where we want to add a custom entitlement. We will call this \u201cCharacteristic,\u201d such as whether the user is tall. We know Trinity is tall, so it\u2019s logical to note their tallness as part of their user attributes.<\/p>\n\n<p>SCIM clients must discover resources through schemas. So, we first need to define the schema describing \u201cCharacteristics.\u201d Note that I came up with \u201cCharacteristics\u201d as the name of this attribute, but you will need to change it for your user entitlements model, whether it be some sort of permissions system or something else. Custom schemas can extend from an existing schema, such as Okta\u2019s entitlement schema, which tracks data as a key-value pair, and add our own flavoring to it.<\/p>\n\n<p>In the IDE, open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/scim-types.ts<\/code>.<\/p>\n\n<p>Add new schema URNs after the <code class=\"language-plaintext highlighter-rouge\">SCHEMA_OKTA_ROLE<\/code> definition towards the end of the file:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">SCHEMA_OKTA_ENTITLEMENT<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">urn:okta:scim:schemas:core:1.0:Entitlement<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">SCHEMA_CHARACTERISTIC<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">urn:bestapps:scim:schemas:extension:todoapp:1.0:Characteristic<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>We defined a new schema URN for the characteristic SCIM resource. Following naming conventions for extension schemas, we substituted our company name (Best Apps) and added the app\u2019s name (Todo app). The format looks like this<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>urn:&lt;Company name&gt;:scim:schemas:extension:&lt;App name&gt;:1.0:&lt;Custom entitlement&gt;\n<\/code><\/pre><\/div><\/div>\n\n<p>Right now, there\u2019s a custom TypeScript type for <code class=\"language-plaintext highlighter-rouge\">RESOURCE_TYPES<\/code>. Since we\u2019ll have custom schemas as a resource type, update the code.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kd\">type<\/span> <span class=\"nx\">RESOURCE_TYPES<\/span> <span class=\"o\">=<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Role<\/span><span class=\"dl\">'<\/span> <span class=\"o\">|<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">ResourceType<\/span><span class=\"dl\">'<\/span> <span class=\"o\">|<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Schema<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>SCIM defines required and optional attributes to describe a schema resource. We\u2019ll define the interfaces for a schema resource. Add the following interfaces to the <code class=\"language-plaintext highlighter-rouge\">scim-types.ts<\/code> file. I added mine after the other interfaces and before the URNs.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kr\">interface<\/span> <span class=\"nx\">ISchema<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nl\">id<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">name<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">description<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">attributes<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IAttribute<\/span><span class=\"p\">[];<\/span>\n  <span class=\"nl\">meta<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IMetadata<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">export<\/span> <span class=\"kr\">interface<\/span> <span class=\"nx\">IAttribute<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nl\">name<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">description<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">type<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">multiValued<\/span><span class=\"p\">:<\/span> <span class=\"nx\">boolean<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">required<\/span><span class=\"p\">:<\/span> <span class=\"nx\">boolean<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">caseExact<\/span><span class=\"p\">:<\/span> <span class=\"nx\">boolean<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">mutability<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">returned<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">uniqueness<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Characteristic is a unique resource type because it\u2019s a new, custom type extending from an existing schema. We must explicitly show this relationship for consuming SCIM clients, like Okta. Find the <code class=\"language-plaintext highlighter-rouge\">IResourceType<\/code> interface. We\u2019ll add a new optional property, <code class=\"language-plaintext highlighter-rouge\">schemaExtensions<\/code> and inline the type definition.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kr\">interface<\/span> <span class=\"nx\">IResourceType<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nl\">id<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">[];<\/span>\n  <span class=\"nl\">name<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">description<\/span><span class=\"p\">?:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">endpoint<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">schema<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">schemaExtensions<\/span><span class=\"p\">?:<\/span> <span class=\"p\">{<\/span><span class=\"na\">schema<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">,<\/span> <span class=\"na\">required<\/span><span class=\"p\">:<\/span> <span class=\"nx\">boolean<\/span><span class=\"p\">}[];<\/span>\n  <span class=\"nl\">meta<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IMetadata<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>SCIM clients expect a list of schemas that you offer in the SCIM server. You might\u2019ve guessed what that means. You must wrap all the schemas in a SCIM <code class=\"language-plaintext highlighter-rouge\">ListResponse<\/code>. Find <code class=\"language-plaintext highlighter-rouge\">IListResponse<\/code> and add <code class=\"language-plaintext highlighter-rouge\">ISchema<\/code> as a supported type. The <code class=\"language-plaintext highlighter-rouge\">IListResponse<\/code> interface changes to:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kr\">interface<\/span> <span class=\"nx\">IListResponse<\/span><span class=\"o\">&lt;<\/span><span class=\"nx\">T<\/span> <span class=\"kd\">extends<\/span> <span class=\"nx\">IScimResource<\/span> <span class=\"o\">|<\/span> <span class=\"nx\">IResourceType<\/span> <span class=\"o\">|<\/span> <span class=\"nx\">ISchema<\/span><span class=\"o\">&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">[];<\/span>\n  <span class=\"nl\">totalResults<\/span><span class=\"p\">:<\/span> <span class=\"kr\">number<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">startIndex<\/span><span class=\"p\">:<\/span> <span class=\"kr\">number<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">itemsPerPage<\/span><span class=\"p\">:<\/span> <span class=\"kr\">number<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">Resources<\/span><span class=\"p\">:<\/span> <span class=\"nx\">T<\/span><span class=\"p\">[];<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Finally, we define what a characteristic attribute looks like by adding the interface shown below.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kr\">interface<\/span> <span class=\"nx\">ICharacteristic<\/span> <span class=\"kd\">extends<\/span> <span class=\"nx\">IScimResource<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nl\">type<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n  <span class=\"nl\">displayName<\/span><span class=\"p\">:<\/span> <span class=\"kr\">string<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>With all the types and interfaces defined, it\u2019s time to write the code for the route. Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/entitlements.ts<\/code>.<\/p>\n\n<p>Update the import array from <code class=\"language-plaintext highlighter-rouge\">.\/scim-types.ts<\/code>:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">import<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">ICharacteristic<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">IListResponse<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">IOktaRole<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">IResourceType<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">ISchema<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">SCHEMA_CHARACTERISTIC<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">SCHEMA_LIST_RESPONSE<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">SCHEMA_OKTA_ENTITLEMENT<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">SCHEMA_OKTA_ROLE<\/span><span class=\"p\">,<\/span>\n  <span class=\"nx\">SCHEMA_RESOURCE_TYPE<\/span>\n<span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">.\/scim-types<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Below the other route definitions, add two new route definitions.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">schemasRoute<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">Router<\/span><span class=\"p\">();<\/span>\n<span class=\"k\">export<\/span> <span class=\"kd\">const<\/span> <span class=\"nx\">characteristicsRoute<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">Router<\/span><span class=\"p\">();<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Now, it\u2019s time to define the <code class=\"language-plaintext highlighter-rouge\">\/Schemas<\/code> route. The <code class=\"language-plaintext highlighter-rouge\">\/Schemas<\/code> endpoint returns a list of schemas. You can return schemas for all the resources you use, even for <code class=\"language-plaintext highlighter-rouge\">User<\/code>, but Okta allows us to skip the strict SCIM requirements and only return custom schemas. The custom schema we\u2019ll return has metadata about a user characteristic, specifically whether the user is tall. Add the following code at the end of the file.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">schemasRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">route<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/<\/span><span class=\"dl\">'<\/span><span class=\"p\">)<\/span>\n  <span class=\"p\">.<\/span><span class=\"kd\">get<\/span><span class=\"p\">((<\/span><span class=\"nx\">_<\/span><span class=\"p\">,<\/span> <span class=\"nx\">res<\/span><span class=\"p\">)<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kd\">const<\/span> <span class=\"na\">characteristic<\/span><span class=\"p\">:<\/span> <span class=\"nx\">ISchema<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"nx\">SCHEMA_CHARACTERISTIC<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Characteristic<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">description<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">User characteristics for entitlements<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">attributes<\/span><span class=\"p\">:<\/span> <span class=\"p\">[{<\/span>\n        <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">is_tall<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">description<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Profile entitlement extension for tallness factor<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">type<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">string<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">multiValued<\/span><span class=\"p\">:<\/span> <span class=\"kc\">false<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">required<\/span><span class=\"p\">:<\/span> <span class=\"kc\">false<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">mutability<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">readWrite<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">returned<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">default<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">caseExact<\/span><span class=\"p\">:<\/span> <span class=\"kc\">false<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">uniqueness<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">none<\/span><span class=\"dl\">'<\/span>\n      <span class=\"p\">}],<\/span>\n      <span class=\"na\">meta<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n        <span class=\"na\">resourceType<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Schema<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">location<\/span><span class=\"p\">:<\/span> <span class=\"s2\">`\/v2\/Schemas\/<\/span><span class=\"p\">${<\/span><span class=\"nx\">SCHEMA_CHARACTERISTIC<\/span><span class=\"p\">}<\/span><span class=\"s2\">`<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">};<\/span>\n\n    <span class=\"kd\">const<\/span> <span class=\"nx\">schemas<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_LIST_RESPONSE<\/span><span class=\"p\">],<\/span>\n      <span class=\"na\">totalResults<\/span><span class=\"p\">:<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">startIndex<\/span><span class=\"p\">:<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">itemsPerPage<\/span><span class=\"p\">:<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">Resources<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span>\n        <span class=\"nx\">characteristic<\/span>\n      <span class=\"p\">]<\/span>\n    <span class=\"p\">};<\/span>\n\n    <span class=\"k\">return<\/span> <span class=\"nx\">res<\/span><span class=\"p\">.<\/span><span class=\"nx\">json<\/span><span class=\"p\">(<\/span><span class=\"nx\">schemas<\/span><span class=\"p\">);<\/span>\n  <span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>And we must define a route for <code class=\"language-plaintext highlighter-rouge\">\/Characteristics<\/code>, in the same way one exists for <code class=\"language-plaintext highlighter-rouge\">\/Roles<\/code>. We won\u2019t worry about updating the database for this as I don\u2019t want to detract from the SCIM concepts. We\u2019ll hardcode the characteristic for now so you can see what this looks like within Okta. Feel free to add the required code to connect it to the database as homework. \ud83c\udfc6 Add the following code below the schemas route:<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">characteristicsRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">route<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/<\/span><span class=\"dl\">'<\/span><span class=\"p\">)<\/span>\n  <span class=\"p\">.<\/span><span class=\"kd\">get<\/span><span class=\"p\">((<\/span><span class=\"nx\">_<\/span><span class=\"p\">,<\/span> <span class=\"nx\">res<\/span><span class=\"p\">)<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kd\">const<\/span> <span class=\"na\">characteristicsListResponse<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IListResponse<\/span><span class=\"o\">&lt;<\/span><span class=\"nx\">ICharacteristic<\/span><span class=\"o\">&gt;<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span>\n        <span class=\"nx\">SCHEMA_OKTA_ENTITLEMENT<\/span><span class=\"p\">,<\/span>\n        <span class=\"nx\">SCHEMA_CHARACTERISTIC<\/span>\n      <span class=\"p\">],<\/span>\n      <span class=\"na\">totalResults<\/span><span class=\"p\">:<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">startIndex<\/span><span class=\"p\">:<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">itemsPerPage<\/span><span class=\"p\">:<\/span> <span class=\"mi\">1<\/span><span class=\"p\">,<\/span>\n      <span class=\"na\">Resources<\/span><span class=\"p\">:<\/span> <span class=\"p\">[{<\/span>\n        <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_CHARACTERISTIC<\/span><span class=\"p\">],<\/span>\n        <span class=\"na\">type<\/span><span class=\"p\">:<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">Characteristic<\/span><span class=\"dl\">\"<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">is_tall<\/span><span class=\"dl\">\"<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">displayName<\/span><span class=\"p\">:<\/span> <span class=\"dl\">\"<\/span><span class=\"s2\">This user is so tall<\/span><span class=\"dl\">\"<\/span>\n      <span class=\"p\">}]<\/span>\n    <span class=\"p\">};<\/span>\n\n    <span class=\"k\">return<\/span> <span class=\"nx\">res<\/span><span class=\"p\">.<\/span><span class=\"nx\">json<\/span><span class=\"p\">(<\/span><span class=\"nx\">characteristicsListResponse<\/span><span class=\"p\">);<\/span>\n  <span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Notice the ID is the string \u201cis_tall\u201d. I modeled it to look like an enum here so that it\u2019s distinct from roles, but IDs in your system may be a UUID or an integer.<\/p>\n\n<p>Lastly, we must add the new characteristic resource type to the <code class=\"language-plaintext highlighter-rouge\">\/ResourceTypes<\/code> response so that Okta knows the resource exists. Find the <code class=\"language-plaintext highlighter-rouge\">resourceTypes.route('\/')<\/code> definition and update the <code class=\"language-plaintext highlighter-rouge\">resourceTypes<\/code> array to include both roles and characteristics.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>  <span class=\"kd\">const<\/span> <span class=\"nx\">resourceTypes<\/span><span class=\"p\">:<\/span> <span class=\"nx\">IResourceType<\/span><span class=\"p\">[]<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[{<\/span>\n    <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_RESOURCE_TYPE<\/span><span class=\"p\">],<\/span>\n    <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Role<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Role<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">endpoint<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">\/Roles<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">description<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Roles you can set on users of Todo App<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">schema<\/span><span class=\"p\">:<\/span> <span class=\"nx\">SCHEMA_OKTA_ROLE<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">meta<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">resourceType<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">ResourceType<\/span><span class=\"dl\">'<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">},<\/span>\n  <span class=\"p\">{<\/span>\n    <span class=\"na\">schemas<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"nx\">SCHEMA_RESOURCE_TYPE<\/span><span class=\"p\">],<\/span>\n    <span class=\"na\">id<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Characteristic<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">name<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Characteristic<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">endpoint<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">\/Characteristics<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">description<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">This resource type is user characteristics<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">schema<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">urn:okta:scim:schemas:core:1.0:Entitlement<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n    <span class=\"na\">schemaExtensions<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span>\n      <span class=\"p\">{<\/span>\n        <span class=\"na\">schema<\/span><span class=\"p\">:<\/span> <span class=\"nx\">SCHEMA_CHARACTERISTIC<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">required<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">],<\/span>\n    <span class=\"na\">meta<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">resourceType<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">ResourceType<\/span><span class=\"dl\">'<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">];<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Now, we must register the routes in the API. Open <code class=\"language-plaintext highlighter-rouge\">okta-enterprise-ready-workshops\/apps\/api\/src\/scim.ts<\/code>. At the top of the file, update the imports from <code class=\"language-plaintext highlighter-rouge\">.\/entitlements<\/code> to<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">import<\/span> <span class=\"p\">{<\/span> <span class=\"nx\">characteristicsRoute<\/span><span class=\"p\">,<\/span> <span class=\"nx\">resourceTypesRoute<\/span><span class=\"p\">,<\/span> <span class=\"nx\">rolesRoute<\/span><span class=\"p\">,<\/span> <span class=\"nx\">schemasRoute<\/span> <span class=\"p\">}<\/span> <span class=\"k\">from<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">.\/entitlements<\/span><span class=\"dl\">'<\/span><span class=\"p\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>At the end of the file, add the code to register the <code class=\"language-plaintext highlighter-rouge\">\/Schemas<\/code> and <code class=\"language-plaintext highlighter-rouge\">\/Charactertistics<\/code> routes to the API.<\/p>\n\n<div class=\"language-ts highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nx\">scimRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">use<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/Schemas<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"nx\">schemasRoute<\/span><span class=\"p\">);<\/span>\n<span class=\"nx\">scimRoute<\/span><span class=\"p\">.<\/span><span class=\"nx\">use<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">\/Characteristics<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"nx\">characteristicsRoute<\/span><span class=\"p\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Serve the API by running <code class=\"language-plaintext highlighter-rouge\">npm run serve-api<\/code> in a terminal window. In a second terminal window, run <code class=\"language-plaintext highlighter-rouge\">npx localtunnel --port 3333<\/code> to create a local tunnel for the API. Keep track of the tunnel URL.<\/p>\n\n<p>Back in the <a href=\"https:\/\/developer.okta.com\/login\/\">Okta Admin console<\/a>, navigate to <strong>Applications<\/strong> &gt; <strong>Applications<\/strong> and open the SCIM with governance Okta app. Navigate to <strong>Provisioning<\/strong> &gt; <strong>Integration<\/strong>. Press <strong>Edit<\/strong> and update the <strong>Base URL<\/strong> using the new tunnel URL. Don\u2019t forget to keep the <code class=\"language-plaintext highlighter-rouge\">\/scim\/v2<\/code> at the end of the URL. The URL should look something like<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>https:\/\/{yourTunnelSubdomain}.loca.lt\/scim\/v2\n<\/code><\/pre><\/div><\/div>\n\n<p>Press <strong>Save<\/strong>.<\/p>\n\n<p>Okta discovers schemas and resource types when updating the provisioning configuration. If you look at the HTTP call tracing in the terminal window serving the API, you\u2019ll see that Okta made a GET request to both <code class=\"language-plaintext highlighter-rouge\">\/Schemas<\/code> and <code class=\"language-plaintext highlighter-rouge\">\/Characteristics<\/code>.<\/p>\n\n<p>Navigate to the <strong>Governance<\/strong>. <strong>Characteristic<\/strong> may take 10-15 minutes to populate, but you\u2019ll see the display name and value when it does. Go <strong>&lt; Back to application<\/strong> and navigate to <strong>Assignments<\/strong>. Open the user context menu for \u201cTrinity\u201d by pressing the three vertical dots icon menu and opening <strong>View entitlements<\/strong>. Press <strong>Edit<\/strong> and <strong>Customize entitlements<\/strong> to add the <code class=\"language-plaintext highlighter-rouge\">is_tall<\/code> user characteristic. <strong>Save<\/strong> the changes and navigate back to the Okta SCIM app.<\/p>\n\n<p>Check out the terminal serving the API for the HTTP call tracing. You\u2019ll see a <code class=\"language-plaintext highlighter-rouge\">PUT<\/code> request on Trinity adding the new characteristic. The field goes into the core SCIM User <code class=\"language-plaintext highlighter-rouge\">entitlements<\/code> property. Check it out by inspecting the HTTP tracing in the console output. \u2705<\/p>\n\n<h2 id=\"multi-tenant-use-cases-for-entitlements\">Multi-tenant use cases for entitlements<\/h2>\n\n<p>In this workshop, we defined roles for the entire Todo app. But what if your SaaS app supports tenant-configurable roles? You must make structural changes to the Todo app database to support organization roles. Notice that an organization has a unique API key, and we included this API as a <code class=\"language-plaintext highlighter-rouge\">Bearer<\/code> token value in the <code class=\"language-plaintext highlighter-rouge\">Authorization<\/code> header. All the SCIM calls from Okta can target a specific organization in the Todo app, including the organization\u2019s custom roles.<\/p>\n\n<table>\n<tr>\n    <td style=\"font-size: 3rem;\">\ufe0f\u2139\ufe0f<\/td>\n    <td>\n      <strong>Note<\/strong> <br \/>\n      We used an API key for demonstration purposes, but we recommend using OAuth to secure the calls from Okta to your API for production applications.\n    <\/td>\n<\/tr>\n<\/table>\n\n<h2 id=\"use-scim-to-manage-user-provisioning-and-entitlements\">Use SCIM to manage user provisioning and entitlements<\/h2>\n\n<p>In this workshop, you dived deeper into SCIM and learned about resources and schemas. You also synced users and their pre-existing entitlements from the Todo app and provisioned users within Okta. I hope you enjoyed this workshop and have ideas for using it for your SaaS applications! Check out the <a href=\"https:\/\/help.okta.com\/oie\/en-us\/content\/topics\/identity-governance\/iga.htm\">Identity Governance<\/a> help docs to learn about Okta Identity Governance.<\/p>\n\n<p>You can find the completed code project in the <a href=\"https:\/\/github.com\/oktadev\/okta-enterprise-ready-workshops\/tree\/entitlements-workshop-complete\"><code class=\"language-plaintext highlighter-rouge\">entitlements-workshop-completed<\/code> branch within the GitHub repo<\/a>.<\/p>\n\n<p>If you want to learn more about what it means to be enterprise-ready and to have enterprise maturity, check out the other workshops in this series<\/p>\n\n<table>\n  <thead>\n    <tr>\n      <th>Posts in the on-demand workshop series<\/th>\n    <\/tr>\n  <\/thead>\n  <tbody>\n    <tr>\n      <td>1. <a href=\"\/blog\/2023\/07\/27\/enterprise-ready-getting-started\">How to Get Going with the On-Demand SaaS Apps Workshops<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>2. <a href=\"\/blog\/2023\/07\/28\/oidc_workshop\">Enterprise-Ready Workshop: Authenticate with OpenID Connect<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>3. <a href=\"\/blog\/2023\/07\/28\/scim-workshop\">Enterprise-Ready Workshop: Manage Users with SCIM<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>4. <a href=\"\/blog\/2023\/07\/28\/terraform-workshop\">Enterprise Maturity Workshop: Terraform<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>5. <a href=\"\/blog\/2023\/09\/15\/workflows-workshop\">Enterprise Maturity Workshop: Automate with no-code Okta Workflows<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>6. <a href=\"\/blog\/2024\/04\/30\/express-universal-logout\">How to Instantly Sign a User Out across All Your Apps<\/a><\/td>\n    <\/tr>\n    <tr>\n      <td>7. <strong>Take User Provisioning to the Next Level with Entitlements<\/strong><\/td>\n    <\/tr>\n  <\/tbody>\n<\/table>\n\n<p>Want to learn about more exciting topics? Let us know by commenting below. To get notified about exciting new content, follow us on <a href=\"https:\/\/twitter.com\/oktadev\">Twitter<\/a> and subscribe to our <a href=\"https:\/\/www.youtube.com\/c\/oktadev\">YouTube<\/a> channel.<\/p>\n","pubDate":"Wed, 21 Jan 2026 00:00:00 -0500","link":"https:\/\/developer.okta.com\/blog\/2026\/01\/21\/user-entitlements-workshop","guid":"https:\/\/developer.okta.com\/blog\/2026\/01\/21\/user-entitlements-workshop"},{"title":"Introducing xaa.dev: A Playground for Cross App Access","description":"<p>AI agents are quickly becoming part of everyday enterprise development. They summarize emails, coordinate calendars, query internal systems, and automate workflows across tools.<\/p>\n\n<p>But once an AI agent needs to access an enterprise application <em>on behalf of a user<\/em>, things get complicated.<\/p>\n\n<p>How do you securely let an AI-powered app act for a user without exposing credentials, spamming consent prompts, or losing administrative control?<\/p>\n\n<p>This is the problem <strong>Cross App Access (XAA)<\/strong> is designed to solve.<\/p>\n\n<p>Today, we\u2019re introducing <strong><a href=\"https:\/\/xaa.dev\">xaa.dev<\/a><\/strong>, a free, open playground that lets you explore Cross App Access end-to-end. <strong>No local setup. No infrastructure to provision.<\/strong> Just a working environment where you can see the protocol in action.<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/xaa-dev-playground\/xaa-dev-homepage-1ced782c627da4675bc483f0b21b24dbb583b30b0fc3978cf753233df80a5cda.jpg\" alt=\"xaa.dev playground homepage showing the Cross App Access flow\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<blockquote>\n  <p><strong>Note:<\/strong> xaa.dev is currently in beta. We\u2019re actively developing new features for the next release, and your feedback helps shape what comes next.<\/p>\n<\/blockquote>\n\n<p><strong class=\"hide\">Table of Contents<\/strong><\/p>\n\n<ul id=\"markdown-toc\">\n  <li><a href=\"#what-is-cross-app-access\" id=\"markdown-toc-what-is-cross-app-access\">What is Cross App Access?<\/a><\/li>\n  <li><a href=\"#the-problem-testing-xaa-is-hard\" id=\"markdown-toc-the-problem-testing-xaa-is-hard\">The problem: testing XAA is hard<\/a><\/li>\n  <li><a href=\"#what-you-can-do-on-xaadev\" id=\"markdown-toc-what-you-can-do-on-xaadev\">What you can do on xaa.dev<\/a>    <ul>\n      <li><a href=\"#requesting-app\" id=\"markdown-toc-requesting-app\">Requesting App<\/a><\/li>\n      <li><a href=\"#resource-app\" id=\"markdown-toc-resource-app\">Resource App<\/a><\/li>\n      <li><a href=\"#identity-provider\" id=\"markdown-toc-identity-provider\">Identity Provider<\/a><\/li>\n      <li><a href=\"#resource-mcp-server\" id=\"markdown-toc-resource-mcp-server\">Resource MCP Server<\/a><\/li>\n      <li><a href=\"#bring-your-own-requesting-app\" id=\"markdown-toc-bring-your-own-requesting-app\">Bring your own Requesting App<\/a><\/li>\n    <\/ul>\n  <\/li>\n  <li><a href=\"#how-to-get-started\" id=\"markdown-toc-how-to-get-started\">How to get started<\/a><\/li>\n  <li><a href=\"#why-we-built-a-testing-site-for-cross-app-access\" id=\"markdown-toc-why-we-built-a-testing-site-for-cross-app-access\">Why we built a testing site for cross app access<\/a><\/li>\n  <li><a href=\"#inspect-the-xaa-flow\" id=\"markdown-toc-inspect-the-xaa-flow\">Inspect the XAA flow<\/a><\/li>\n  <li><a href=\"#learn-more\" id=\"markdown-toc-learn-more\">Learn more<\/a><\/li>\n<\/ul>\n\n<h2 id=\"what-is-cross-app-access\">What is Cross App Access?<\/h2>\n\n<p>Cross App Access refers to a typical enterprise pattern: <strong>one application accesses another application\u2019s resources on behalf of a user.<\/strong><\/p>\n\n<p>For example:<\/p>\n\n<ul>\n  <li>An internal AI assistant fetching updates from a project management system<\/li>\n  <li>A workflow engine booking meetings through a calendar API<\/li>\n  <li>An agent querying internal data sources to complete a task<\/li>\n<\/ul>\n\n<p>Traditionally, OAuth consent flows handle this. That approach works well for consumer-based apps, but it creates friction in enterprise environments where organizations require workforce oversight:<\/p>\n\n<ul>\n  <li>Applications and their access levels are centrally managed<\/li>\n  <li>IT teams need visibility into trust relationships<\/li>\n  <li>Access must be revocable without user involvement<\/li>\n<\/ul>\n\n<p>Cross App Access shifts responsibility from end users to the enterprise identity layer.<\/p>\n\n<p>Instead of prompting users for consent, the <strong>Identity Provider (IdP)<\/strong> issues a signed identity assertion called an <strong>ID-JAG (Identity JWT Authorization Grant)<\/strong>. This assertion cryptographically represents the user and the requesting application. Resource applications trust the IdP\u2019s assertion and issue access accordingly.<\/p>\n\n<p>The result:<\/p>\n\n<ul>\n  <li>No interactive consent screens making application access seamless for employees<\/li>\n  <li>Clear, auditable trust boundaries<\/li>\n  <li>Complete administrative control over app-to-app access<\/li>\n<\/ul>\n\n<p>For a deeper dive into why this matters for enterprise AI, read more about Cross App Access in this post:<\/p>\n\n<article class=\"link-container\" style=\"border: 1px solid silver; border-radius: 3px; padding: 12px 15px\">\n              <a href=\"\/blog\/2025\/06\/23\/enterprise-ai\" style=\"font-size: 1.375em; margin-bottom: 20px;\">\n                <span>Integrate Your Enterprise AI Tools with Cross-App Access<\/span>\n              <\/a>\n              <p>Manage user and non-human identities, including AI in the enterprise with Cross App Access<\/p>\n              <div><div class=\"BlogPost-attribution\">\n            <a href=\"\/blog\/authors\/semona-igama\/\">\n              <img src=\"\/assets-jekyll\/avatar-semona-igama-03eb4c28aca3765f862b574e032d32f6f8186d04ae9f0db75bed9c74f48a9a3f.jpg\" alt=\"avatar-avatar-semona-igama.jpeg\" class=\"BlogPost-avatar\" \/>\n            <\/a>\n            <span class=\"BlogPost-author\">\n                <a href=\"\/blog\/authors\/semona-igama\/\">Semona Igama<\/a>\n            <\/span>\n          <\/div><\/div>\n          <\/article>\n\n<h2 id=\"the-problem-testing-xaa-is-hard\">The problem: testing XAA is hard<\/h2>\n\n<p>XAA is built on an emerging OAuth extension called the <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/draft-ietf-oauth-identity-assertion-authz-grant\">Identity Assertion JWT Authorization Grant<\/a> \u2013 an IETF draft that Okta, along with public and industry contributors, has been actively contributing to. It\u2019s powerful, but it\u2019s also new, and new protocols need experimentation.<\/p>\n\n<p>Here\u2019s the challenge: to test XAA locally, you\u2019d need to spin up:<\/p>\n\n<ul>\n  <li>An Identity Provider (IdP)<\/li>\n  <li>An Authorization Server for the resource application<\/li>\n  <li>The resource API itself<\/li>\n  <li>A requesting application (the agent or client app)<\/li>\n<\/ul>\n\n<p>That\u2019s hours (or days) of configuration before you can even see a single token exchange. Most developers give up before getting to the interesting part.<\/p>\n\n<p><strong>xaa.dev changes that.<\/strong><\/p>\n\n<p>We pre-configured all the components so you can focus on understanding the flow, not debugging dev environments. Go from zero to a working XAA token exchange in under 60 seconds.<\/p>\n\n<p><strong><a href=\"https:\/\/xaa.dev\">Launch the playground<\/a><\/strong>. It\u2019s free and requires no signup.<\/p>\n\n<h2 id=\"what-you-can-do-on-xaadev\">What you can do on xaa.dev<\/h2>\n\n<p>The playground gives you hands-on access to every role in the Cross App Access flow:<\/p>\n\n<h3 id=\"requesting-app\">Requesting App<\/h3>\n<p>Step into the shoes of an AI agent or client application. Authenticate a user, request an ID-JAG from the IdP, and exchange it for an access token at the resource server.<\/p>\n\n<h3 id=\"resource-app\">Resource App<\/h3>\n<p>See the other side of the transaction. Watch how a resource server validates the identity assertion, verifies the trust relationship, and issues scoped access tokens.<\/p>\n\n<h3 id=\"identity-provider\">Identity Provider<\/h3>\n<p>We\u2019ve built a simulated IdP with pre-configured test users. Log in, see how ID-JAGs are minted, and inspect the cryptographic claims that make XAA secure.<\/p>\n\n<h3 id=\"resource-mcp-server\">Resource MCP Server<\/h3>\n<p>Connect your AI agents using the Model Context Protocol (MCP). The playground provides a ready-to-use MCP server that acts as a resource application, letting you test how AI agents can securely access protected resources through the Cross App Access flow.<\/p>\n\n<h3 id=\"bring-your-own-requesting-app\">Bring your own Requesting App<\/h3>\n<p>The built-in Requesting App is great for learning, but the real power comes when you test with your own application, whether it\u2019s a traditional app or an MCP client. <a href=\"https:\/\/xaa.dev\/developer\/register\">Register a client<\/a> on the playground, grab the configuration, and integrate it into your local app. This lets you validate your XAA implementation against a working IdP and Resource App without spinning up your own infrastructure. The <a href=\"https:\/\/xaa.dev\/docs\">playground documentation<\/a> walks you through the setup step-by-step.<\/p>\n\n<h2 id=\"how-to-get-started\">How to get started<\/h2>\n\n<p>Getting started with xaa.dev takes less than a minute:<\/p>\n\n<p><strong>Step 1: Open the playground<\/strong><\/p>\n\n<p>Visit <a href=\"https:\/\/xaa.dev\">xaa.dev<\/a>. No account required.<\/p>\n\n<p><strong>Step 2: Explore the components<\/strong><\/p>\n\n<p>The playground has three components (Requesting App, Resource App, and Identity Provider), each with its own URL. Visit any component to see its configuration and understand how it participates in the XAA flow.<\/p>\n\n<p><strong>Step 3: Follow the guided flow<\/strong><\/p>\n\n<p>Walk through the four steps of the XAA flow: User Authentication (SSO), Token Exchange, Access Token Request, and Access Resource. Inspect the requests and responses at each step to see exactly how XAA works under the hood.<\/p>\n\n<p>That\u2019s it. No local tools installations, Docker containers, environment variables, or CORS headaches.<\/p>\n\n<p>Watch this walkthrough video of the playground if you\u2019d like a guided tour:<\/p>\n\n<div class=\"jekyll-youtube-plugin\" style=\"text-align: center; margin-bottom: 1.25rem\">\n            <iframe width=\"700\" height=\"394\" style=\"max-width: 100%\" src=\"https:\/\/www.youtube.com\/embed\/hXZ4o2Oasc0\" allowfullscreen=\"\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" frameborder=\"0\"><\/iframe>\n        <\/div>\n\n<h2 id=\"why-we-built-a-testing-site-for-cross-app-access\">Why we built a testing site for cross app access<\/h2>\n\n<p>XAA is built on an emerging IETF specification, the Identity Assertion JWT Authorization Grant. As enterprise AI adoption accelerates, there\u2019s a clear need: developers want to understand XAA, but the barrier to entry is too high.<\/p>\n\n<p>xaa.dev lowers the barrier. It helps you:<\/p>\n\n<ul>\n  <li><strong>Learn faster<\/strong> \u2013 See the protocol in action before writing any code<\/li>\n  <li><strong>Build confidently<\/strong> \u2013 Understand exactly what tokens to expect and validate<\/li>\n  <li><strong>Experiment safely<\/strong> \u2013 Test edge cases without affecting production systems<\/li>\n<\/ul>\n\n<h2 id=\"inspect-the-xaa-flow\">Inspect the XAA flow<\/h2>\n\n<p>XAA is how enterprise applications will securely connect in an AI-first world. Whether you\u2019re building agents, integrating SaaS tools, or just curious about modern OAuth patterns, <a href=\"https:\/\/xaa.dev\">xaa.dev<\/a> gives you a risk-free environment to learn. Check it out and let us know how it works for you!<\/p>\n\n<h2 id=\"learn-more\">Learn more<\/h2>\n\n<p>Ready to go deeper? Check out these resources:<\/p>\n\n<ul>\n  <li><a href=\"https:\/\/www.okta.com\/integrations\/cross-app-access\/\">Checkout Cross App Access Integration in Okta <\/a> \u2013 Securing AI-driven access together<\/li>\n  <li><a href=\"\/blog\/2025\/09\/03\/cross-app-access\">Build Secure Agent-to-App Connections with Cross App Access<\/a> \u2013 Hands-on implementation guide<\/li>\n  <li><a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/draft-ietf-oauth-identity-assertion-authz-grant\">Identity Assertion JWT Authorization Grant (IETF Draft)<\/a> \u2013 The specification behind XAA<\/li>\n<\/ul>\n\n<p>Have questions or feedback? Reach out to us on <a href=\"https:\/\/twitter.com\/oktadev\">Twitter<\/a>, join the conversation on the <a href=\"https:\/\/devforum.okta.com\/\">Okta Developer Forums<\/a>, or drop a comment below. We\u2019re actively improving xaa.dev based on developer input \u2013 your feedback shapes what we build next.<\/p>\n\n<p>Follow us on <a href=\"https:\/\/twitter.com\/oktadev\">Twitter<\/a> and subscribe to our <a href=\"https:\/\/youtube.com\/oktadev\">YouTube channel<\/a> for more content on identity, security, and building with Okta.<\/p>\n","pubDate":"Tue, 20 Jan 2026 00:00:00 -0500","link":"https:\/\/developer.okta.com\/blog\/2026\/01\/20\/xaa-dev-playground","guid":"https:\/\/developer.okta.com\/blog\/2026\/01\/20\/xaa-dev-playground"},{"title":"Okta Developer Connect Recap","description":"<p>Identity has become one of the most important control points in modern systems. As applications grow more distributed and AI-driven automation becomes part of everyday workflows, identity increasingly defines how secure, predictable, and trustworthy those systems are. Decisions about access, scope, and lifecycle now shape not only the user experience, but also how well security holds up as systems scale.<\/p>\n\n<p>With this shift in mind, we hosted our first flagship <strong><a href=\"https:\/\/regionalevents.okta.com\/oktadeveloperconnect\">Okta Developer Connect event<\/a><\/strong> in India, in Bengaluru. Led by the Okta Developer Advocacy team, the event brought together developers, architects, engineering managers, IAM practitioners, and technology leaders for a day of focused conversations on how identity needs to evolve as systems scale, and as AI agents begin to act alongside humans.<\/p>\n\n<h2 id=\"where-identity-security-and-ai-connect\">Where identity, security, and AI connect<\/h2>\n\n<p>Throughout the day, the theme stood out clearly: identity now sits at the intersection of application architecture, security decisions, and system behavior. As organizations integrate across ecosystems and introduce AI-driven workflows, identity increasingly determines how safely systems interact and how confidently access can be controlled.<\/p>\n\n<p>Identity was discussed as foundational infrastructure, spanning users, applications, APIs, services, and AI agents. The conversations focused on how identity decisions ripple across systems as they become more interconnected, and why those decisions matter long after the first sign-in.<\/p>\n\n<p>The sessions balanced identity fundamentals with how teams are applying them today. Core identity standards and practices were discussed as the foundation for building secure, interoperable systems that scale. From there, the conversations expanded into modern use cases across Okta\u2019s platform, including identity for AI-driven systems using the Okta MCP Server, Cross App Access, secure integrations through the Okta Integration Network, automation with Okta Workflows, lifecycle management, and emerging capabilities such as Verifiable Digital Credentials.<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/okta-developer-connect-recap\/image-1-063315f1150424b260fa798d1e4ebe0d6e781da15af736d3db6e68fbe4b2d234.jpg\" alt=\"speakers\" width=\"700\" class=\"center-image\" \/><\/p>\n\n<h2 id=\"a-shared-conversation-with-the-community\">A shared conversation with the community<\/h2>\n<p>Beyond the talks, Okta Developer Connect was intentionally designed to be interactive, with open discussions, audience Q&amp;A, quizzes, interactive sessions, surveys, and a research panel created space for participation beyond listening. Attendees engaged directly with Okta engineers, product leaders, and advocates, exchanging perspectives shaped by the systems they build and operate. Those exchanges added important context to the day, grounding the conversations in real systems and practical implementations.<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/okta-developer-connect-recap\/image-2-0ac56c03de4e4dcbf532f691d9ab6992ca6c26c3470cdf839cf04cbf1e271abf.jpg\" alt=\"community\" width=\"700\" class=\"center-image\" \/><\/p>\n\n<h2 id=\"looking-ahead\">Looking ahead<\/h2>\n<p>Okta Developer Connect Bengaluru marked the beginning of an ongoing series focused on practical identity education and open technical dialogue. The series aims to help teams think more clearly about building enterprise-ready systems, how identity standards and practices are reflected across the stack, and how AI-driven systems impact access and security assumptions.<\/p>\n\n<p>As identity continues to sit at the intersection of security, system architecture, and AI-driven automation, these conversations will only become more important.<\/p>\n\n<p>We\u2019re excited to continue building this forum with the developer community, grounded in standards, informed by real-world systems, and focused on designing identity that scales with what comes next.<\/p>\n\n<p>Stay tuned for upcoming Okta Developer Connect events, and follow OktaDev on <a href=\"https:\/\/www.linkedin.com\/company\/oktadev\">LinkedIn<\/a> and <a href=\"https:\/\/twitter.com\/oktadev\">Twitter<\/a> for the latest updates.<\/p>\n","pubDate":"Mon, 19 Jan 2026 00:00:00 -0500","link":"https:\/\/developer.okta.com\/blog\/2026\/01\/19\/okta-developer-connect-recap","guid":"https:\/\/developer.okta.com\/blog\/2026\/01\/19\/okta-developer-connect-recap"},{"title":"Unlock the Secrets of a Custom Sign-In Page with Tailwind and JavaScript","description":"<p>We recommend redirecting users to authenticate via the Okta-hosted sign-in page powered by the Okta Identity Engine (OIE) for your custom-built applications. It\u2019s the most secure method for authenticating. You don\u2019t have to manage credentials in your code and can take advantage of the strongest authentication factors without requiring any code changes.<\/p>\n\n<p>The Okta Sign-In Widget (SIW) built into the sign-in page does the heavy lifting of supporting the authentication factors required by your organization. Did I mention policy changes won\u2019t need any code changes?<\/p>\n\n<p>But you may think the sign-in page and the SIW are a little bland. And maybe too Okta for your needs? What if you can have a page like this?<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/okta-custom-sign-in-page\/final-siw-desktop-211b475e04926250d77e2a72779c27ea2c22a65ad47f43322c22ba81006f5df2.jpg\" alt=\"A customized Okta-hosted Sign-In Widget with custom elements, colors, and styles\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>With a bright and colorful responsive design change befitting a modern lifestyle.<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/okta-custom-sign-in-page\/final-siw-responsive-c258e3e1daf1d1fdfe36dfbaa4eb61afd13e38e5234b3781af4ac08bbd6baa13.jpg\" alt=\"A customized Okta-hosted Sign-In Widget with custom elements, colors, and styles for smaller form factors\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>Let\u2019s add some color, life, and customization to the sign-in page.<\/p>\n\n<p>In this tutorial, we will customize the sign-in page for a fictional to-do app. We\u2019ll make the following changes:<\/p>\n<ul>\n  <li>Use <a href=\"https:\/\/tailwindcss.com\/\">Tailwind<\/a> CSS framework to create a responsive sign-in page layout<\/li>\n  <li>Add a footer for custom brand links<\/li>\n  <li>Display a terms and conditions modal using <a href=\"https:\/\/alpinejs.dev\">Alpine.js<\/a> that the user must accept before authenticating<\/li>\n<\/ul>\n\n<p>Take a moment to read this post on customizing the Sign-In Widget if you aren\u2019t familiar with the process, as we will be expanding from customizing the widget to enhancing the entire sign-in page experience.<\/p>\n\n<article class=\"link-container\" style=\"border: 1px solid silver; border-radius: 3px; padding: 12px 15px\">\n              <a href=\"\/blog\/2025\/11\/12\/custom-signin\" style=\"font-size: 1.375em; margin-bottom: 20px;\">\n                <span>Stretch Your Imagination and Build a Delightful Sign-In Experience<\/span>\n              <\/a>\n              <p>Customize your Gen3 Okta Sign-In Widget to match your brand. Learn to use design tokens, CSS, and JavaScript for a seamless user experience.<\/p>\n              <div><\/div>\n          <\/article>\n\n<p>In the post, we covered how to style the Gen3 SIW using design tokens and customize the widget elements using the <code class=\"language-plaintext highlighter-rouge\">afterTransform()<\/code> method. You\u2019ll want to combine elements of both posts for the most customized experience.<\/p>\n\n<p><strong class=\"hide\">Table of Contents<\/strong><\/p>\n<ul id=\"markdown-toc\">\n  <li><a href=\"#customize-your-okta-hosted-sign-in-page\" id=\"markdown-toc-customize-your-okta-hosted-sign-in-page\">Customize your Okta-hosted sign-in page<\/a><\/li>\n  <li><a href=\"#use-tailwind-css-to-build-a-responsive-layout\" id=\"markdown-toc-use-tailwind-css-to-build-a-responsive-layout\">Use Tailwind CSS to build a responsive layout<\/a><\/li>\n  <li><a href=\"#use-tailwind-for-custom-html-elements-on-your-okta-hosted-sign-in-page\" id=\"markdown-toc-use-tailwind-for-custom-html-elements-on-your-okta-hosted-sign-in-page\">Use Tailwind for custom HTML elements on your Okta-hosted sign-in page<\/a><\/li>\n  <li><a href=\"#add-custom-interactivity-on-the-okta-hosted-sign-in-page-using-an-external-library\" id=\"markdown-toc-add-custom-interactivity-on-the-okta-hosted-sign-in-page-using-an-external-library\">Add custom interactivity on the Okta-hosted sign-in page using an external library<\/a><\/li>\n  <li><a href=\"#customize-okta-hosted-sign-in-page-behavior-using-web-apis\" id=\"markdown-toc-customize-okta-hosted-sign-in-page-behavior-using-web-apis\">Customize Okta-hosted sign-in page behavior using Web APIs<\/a><\/li>\n  <li><a href=\"#add-tailwind-web-apis-and-javascript-libraries-to-customize-your-okta-hosted-sign-in-page\" id=\"markdown-toc-add-tailwind-web-apis-and-javascript-libraries-to-customize-your-okta-hosted-sign-in-page\">Add Tailwind, Web APIs, and JavaScript libraries to customize your Okta-hosted sign-in page<\/a><\/li>\n<\/ul>\n\n<p><strong>Prerequisites<\/strong><\/p>\n\n<p>To follow this tutorial, you need:<\/p>\n<ul>\n  <li>An Okta account with the Identity Engine, such as the <a href=\"https:\/\/developer.okta.com\/signup\/\">Integrator Free account<\/a>.<\/li>\n  <li>Your own domain name<\/li>\n  <li>A basic understanding of HTML, CSS, and JavaScript<\/li>\n  <li>A brand design in mind. Feel free to tap into your creativity!<\/li>\n  <li>An understanding of customizing the sign-in page by following the previous blog post<\/li>\n<\/ul>\n\n<p>Let\u2019s get started!<\/p>\n\n<p>Before we begin, you must configure your Okta org to use your custom domain. Custom domains enable code customizations, allowing us to style more than just the default logo, background, favicon, and two colors. Sign in as an admin and open the Okta Admin Console, navigate to <strong>Customizations<\/strong> &gt; <strong>Brands<\/strong> and select <strong>Create Brand +<\/strong>.<\/p>\n\n<p>Follow the <a href=\"https:\/\/developer.okta.com\/docs\/guides\/custom-url-domain\/main\/\">Customize domain and email<\/a> developer docs to set up your custom domain on the new brand.<\/p>\n\n<h2 id=\"customize-your-okta-hosted-sign-in-page\">Customize your Okta-hosted sign-in page<\/h2>\n\n<p>We\u2019ll first apply the base configuration using the built-in configuration options in the UI. Add your favorite primary and secondary colors, then upload your favorite logo, favicon, and background image for the page. Select <strong>Save<\/strong> when done. Everyone has a favorite favicon, right?<\/p>\n\n<p>I\u2019ll use <code class=\"language-plaintext highlighter-rouge\">#ea3eda<\/code> and <code class=\"language-plaintext highlighter-rouge\">#ffa738<\/code> as the primary and secondary colors, respectively.<\/p>\n\n<p>On to the code. In the <strong>Theme<\/strong> tab:<\/p>\n<ol>\n  <li>Select <strong>Sign-in Page<\/strong> in the dropdown menu<\/li>\n  <li>Select the <strong>Customize<\/strong> button<\/li>\n  <li>On the <strong>Page Design<\/strong> tab, select the <strong>Code editor<\/strong>  toggle to see a HTML page<\/li>\n<\/ol>\n\n<blockquote>\n  <p><strong>Note<\/strong><\/p>\n\n  <p>You can only enable the code editor if you configure a <a href=\"https:\/\/developer.okta.com\/docs\/guides\/custom-url-domain\/\">custom domain<\/a>.<\/p>\n<\/blockquote>\n\n<p>You\u2019ll see the lightweight IDE already has code scaffolded. Press <strong>Edit<\/strong> and replace the existing code with the following.<\/p>\n\n<div class=\"language-html highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cp\">&lt;!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD HTML 4.01\/\/EN\" \"http:\/\/www.w3.org\/TR\/html4\/strict.dtd\"&gt;<\/span>\n<span class=\"nt\">&lt;html&gt;<\/span>\n\n<span class=\"nt\">&lt;head&gt;<\/span>\n  <span class=\"nt\">&lt;meta<\/span> <span class=\"na\">http-equiv=<\/span><span class=\"s\">\"Content-Type\"<\/span> <span class=\"na\">content=<\/span><span class=\"s\">\"text\/html; charset=UTF-8\"<\/span><span class=\"nt\">&gt;<\/span>\n  <span class=\"nt\">&lt;meta<\/span> <span class=\"na\">name=<\/span><span class=\"s\">\"viewport\"<\/span> <span class=\"na\">content=<\/span><span class=\"s\">\"width=device-width, initial-scale=1.0\"<\/span> <span class=\"nt\">\/&gt;<\/span>\n  <span class=\"nt\">&lt;meta<\/span> <span class=\"na\">name=<\/span><span class=\"s\">\"robots\"<\/span> <span class=\"na\">content=<\/span><span class=\"s\">\"noindex,nofollow\"<\/span> <span class=\"nt\">\/&gt;<\/span>\n  <span class=\"c\">&lt;!-- Styles generated from theme --&gt;<\/span>\n  <span class=\"nt\">&lt;link<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"{{themedStylesUrl}}\"<\/span> <span class=\"na\">rel=<\/span><span class=\"s\">\"stylesheet\"<\/span> <span class=\"na\">type=<\/span><span class=\"s\">\"text\/css\"<\/span><span class=\"nt\">&gt;<\/span>\n  <span class=\"c\">&lt;!-- Favicon from theme --&gt;<\/span>\n  <span class=\"nt\">&lt;link<\/span> <span class=\"na\">rel=<\/span><span class=\"s\">\"shortcut icon\"<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"{{faviconUrl}}\"<\/span> <span class=\"na\">type=<\/span><span class=\"s\">\"image\/x-icon\"<\/span><span class=\"nt\">&gt;<\/span>\n  <span class=\"nt\">&lt;link<\/span> <span class=\"na\">rel=<\/span><span class=\"s\">\"preconnect\"<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"https:\/\/fonts.googleapis.com\"<\/span><span class=\"nt\">&gt;<\/span>\n  <span class=\"nt\">&lt;link<\/span> <span class=\"na\">rel=<\/span><span class=\"s\">\"preconnect\"<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"https:\/\/fonts.gstatic.com\"<\/span> <span class=\"na\">crossorigin<\/span><span class=\"nt\">&gt;<\/span>\n  <span class=\"nt\">&lt;link<\/span>\n      <span class=\"na\">href=<\/span><span class=\"s\">\"https:\/\/fonts.googleapis.com\/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&amp;family=Manrope:wght@200..800&amp;display=swap\"<\/span>\n      <span class=\"na\">rel=<\/span><span class=\"s\">\"stylesheet\"<\/span><span class=\"nt\">&gt;<\/span>\n  <span class=\"nt\">&lt;title&gt;<\/span>{{pageTitle}}<span class=\"nt\">&lt;\/title&gt;<\/span>\n  {{{SignInWidgetResources}}}\n\n  <span class=\"nt\">&lt;style <\/span><span class=\"na\">nonce=<\/span><span class=\"s\">\"{{nonceValue}}\"<\/span><span class=\"nt\">&gt;<\/span>\n    <span class=\"nd\">:root<\/span> <span class=\"p\">{<\/span>\n      <span class=\"py\">--font-header<\/span><span class=\"p\">:<\/span> <span class=\"s2\">'Inter Tight'<\/span><span class=\"p\">,<\/span> <span class=\"nb\">sans-serif<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--font-body<\/span><span class=\"p\">:<\/span> <span class=\"s2\">'Manrope'<\/span><span class=\"p\">,<\/span> <span class=\"nb\">sans-serif<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--color-gray<\/span><span class=\"p\">:<\/span> <span class=\"m\">#4f4f4f<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--color-fuchsia<\/span><span class=\"p\">:<\/span> <span class=\"m\">#ea3eda<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--color-orange<\/span><span class=\"p\">:<\/span> <span class=\"m\">#ffa738<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--color-azul<\/span><span class=\"p\">:<\/span> <span class=\"m\">#016fb9<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--color-cherry<\/span><span class=\"p\">:<\/span> <span class=\"m\">#ea3e84<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--color-purple<\/span><span class=\"p\">:<\/span> <span class=\"m\">#b13fff<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--color-black<\/span><span class=\"p\">:<\/span> <span class=\"m\">#191919<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--color-white<\/span><span class=\"p\">:<\/span> <span class=\"m\">#fefefe<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--color-bright-white<\/span><span class=\"p\">:<\/span> <span class=\"m\">#fff<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--border-radius<\/span><span class=\"p\">:<\/span> <span class=\"m\">4px<\/span><span class=\"p\">;<\/span>\n      <span class=\"py\">--color-gradient<\/span><span class=\"p\">:<\/span> <span class=\"n\">linear-gradient<\/span><span class=\"p\">(<\/span><span class=\"m\">12deg<\/span><span class=\"p\">,<\/span> <span class=\"n\">var<\/span><span class=\"p\">(<\/span><span class=\"n\">--color-fuchsia<\/span><span class=\"p\">)<\/span> <span class=\"m\">0%<\/span><span class=\"p\">,<\/span> <span class=\"n\">var<\/span><span class=\"p\">(<\/span><span class=\"n\">--color-orange<\/span><span class=\"p\">)<\/span> <span class=\"m\">100%<\/span><span class=\"p\">);<\/span>\n    <span class=\"p\">}<\/span>\n\n    <span class=\"p\">{<\/span><span class=\"err\">{#useSiwGen3<\/span><span class=\"p\">}<\/span><span class=\"err\">}<\/span>\n      <span class=\"nt\">html<\/span> <span class=\"p\">{<\/span>\n        <span class=\"nl\">font-size<\/span><span class=\"p\">:<\/span> <span class=\"m\">87.5%<\/span><span class=\"p\">;<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">{<\/span><span class=\"err\">{\/useSiwGen3<\/span><span class=\"p\">}<\/span><span class=\"err\">}<\/span>\n\n    <span class=\"nf\">#okta-auth-container<\/span> <span class=\"p\">{<\/span>\n      <span class=\"nl\">display<\/span><span class=\"p\">:<\/span> <span class=\"n\">flex<\/span><span class=\"p\">;<\/span>\n      <span class=\"nl\">background-image<\/span><span class=\"p\">:<\/span> <span class=\"err\">{{<\/span><span class=\"n\">bgImageUrl<\/span><span class=\"p\">}<\/span><span class=\"err\">}<\/span><span class=\"o\">;<\/span>\n    <span class=\"err\">}<\/span>\n\n    <span class=\"nf\">#okta-login-container<\/span> <span class=\"p\">{<\/span>\n      <span class=\"nl\">display<\/span><span class=\"p\">:<\/span> <span class=\"n\">flex<\/span><span class=\"p\">;<\/span>\n      <span class=\"nl\">justify-content<\/span><span class=\"p\">:<\/span> <span class=\"nb\">center<\/span><span class=\"p\">;<\/span>\n      <span class=\"nl\">align-items<\/span><span class=\"p\">:<\/span> <span class=\"nb\">center<\/span><span class=\"p\">;<\/span>\n      <span class=\"nl\">height<\/span><span class=\"p\">:<\/span> <span class=\"m\">100vh<\/span><span class=\"p\">;<\/span>\n      <span class=\"nl\">width<\/span><span class=\"p\">:<\/span> <span class=\"m\">50vw<\/span><span class=\"p\">;<\/span>\n      <span class=\"nl\">background<\/span><span class=\"p\">:<\/span> <span class=\"n\">var<\/span><span class=\"p\">(<\/span><span class=\"n\">--color-white<\/span><span class=\"p\">);<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"nt\">&lt;\/style&gt;<\/span>\n<span class=\"nt\">&lt;\/head&gt;<\/span>\n\n<span class=\"nt\">&lt;body&gt;<\/span>  \n  <span class=\"nt\">&lt;div<\/span> <span class=\"na\">id=<\/span><span class=\"s\">\"okta-auth-container\"<\/span><span class=\"nt\">&gt;<\/span>\n    <span class=\"nt\">&lt;div<\/span> <span class=\"na\">id=<\/span><span class=\"s\">\"okta-login-container\"<\/span><span class=\"nt\">&gt;&lt;\/div&gt;<\/span>      \n  <span class=\"nt\">&lt;\/div&gt;<\/span>\n    \n  <span class=\"c\">&lt;!--\n   \"OktaUtil\" defines a global OktaUtil object\n   that contains methods used to complete the Okta login flow.\n  --&gt;<\/span>\n  {{{OktaUtil}}}\n\n  <span class=\"nt\">&lt;script <\/span><span class=\"na\">type=<\/span><span class=\"s\">\"text\/javascript\"<\/span> <span class=\"na\">nonce=<\/span><span class=\"s\">\"{{nonceValue}}\"<\/span><span class=\"nt\">&gt;<\/span>\n    <span class=\"c1\">\/\/ \"config\" object contains default widget configuration<\/span>\n    <span class=\"c1\">\/\/ with any custom overrides defined in your admin settings.<\/span>\n\n    <span class=\"kd\">const<\/span> <span class=\"nx\">config<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">OktaUtil<\/span><span class=\"p\">.<\/span><span class=\"nx\">getSignInWidgetConfig<\/span><span class=\"p\">();<\/span>\n    <span class=\"nx\">config<\/span><span class=\"p\">.<\/span><span class=\"nx\">theme<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n      <span class=\"na\">tokens<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n        <span class=\"na\">BorderColorDisplay<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--color-bright-white)<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">PalettePrimaryMain<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--color-fuchsia)<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">PalettePrimaryDark<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--color-purple)<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">PalettePrimaryDarker<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--color-purple)<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">BorderRadiusTight<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--border-radius)<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">BorderRadiusMain<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--border-radius)<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">PalettePrimaryDark<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--color-orange)<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">FocusOutlineColorPrimary<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--color-azul)<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">TypographyFamilyBody<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--font-body)<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">TypographyFamilyHeading<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--font-header)<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">TypographyFamilyButton<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--font-header)<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"na\">BorderColorDangerControl<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">var(--color-cherry)<\/span><span class=\"dl\">'<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n\n    <span class=\"nx\">config<\/span><span class=\"p\">.<\/span><span class=\"nx\">i18n<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n      <span class=\"dl\">'<\/span><span class=\"s1\">en<\/span><span class=\"dl\">'<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n        <span class=\"dl\">'<\/span><span class=\"s1\">primaryauth.title<\/span><span class=\"dl\">'<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">Log in to create tasks<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n\n    <span class=\"c1\">\/\/ Render the Okta Sign-In Widget<\/span>\n    <span class=\"kd\">const<\/span> <span class=\"nx\">oktaSignIn<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nx\">OktaSignIn<\/span><span class=\"p\">(<\/span><span class=\"nx\">config<\/span><span class=\"p\">);<\/span>\n    <span class=\"nx\">oktaSignIn<\/span><span class=\"p\">.<\/span><span class=\"nx\">renderEl<\/span><span class=\"p\">({<\/span> <span class=\"na\">el<\/span><span class=\"p\">:<\/span> <span class=\"dl\">'<\/span><span class=\"s1\">#okta-login-container<\/span><span class=\"dl\">'<\/span> <span class=\"p\">},<\/span>\n      <span class=\"nx\">OktaUtil<\/span><span class=\"p\">.<\/span><span class=\"nx\">completeLogin<\/span><span class=\"p\">,<\/span>\n      <span class=\"kd\">function<\/span> <span class=\"p\">(<\/span><span class=\"nx\">error<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"c1\">\/\/ Logs errors that occur when configuring the widget.<\/span>\n        <span class=\"c1\">\/\/ Remove or replace this with your own custom error handler.<\/span>\n        <span class=\"nx\">console<\/span><span class=\"p\">.<\/span><span class=\"nx\">log<\/span><span class=\"p\">(<\/span><span class=\"nx\">error<\/span><span class=\"p\">.<\/span><span class=\"nx\">message<\/span><span class=\"p\">,<\/span> <span class=\"nx\">error<\/span><span class=\"p\">);<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">);<\/span>\n  <span class=\"nt\">&lt;\/script&gt;<\/span>\n<span class=\"nt\">&lt;\/body&gt;<\/span>\n<span class=\"nt\">&lt;\/html&gt;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This code adds style configuration to the SIW elements and configures the text for the title when signing in. Press <strong>Save to draft<\/strong>.<\/p>\n\n<p>We must allow Okta to load font resources from an external source, Google, by adding the domains to the allowlist in the Content Security Policy (CSP).<\/p>\n\n<p>Navigate to the <strong>Settings<\/strong> tab for your brand\u2019s <strong>Sign-in page<\/strong>. Find the <strong>Content Security Policy<\/strong> and press <strong>Edit<\/strong>. Add the domains for external resources. In our example, we only load resources from Google Fonts, so we added the following two domains:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>*.googleapis.com\n*.gstatic.com\n<\/code><\/pre><\/div><\/div>\n\n<p>Select <strong>Save to draft<\/strong>, then <strong>Publish<\/strong> to view your changes.<\/p>\n\n<p>The sign-in page looks more stylized than before. If you try resizing the browser window, we see it\u2019s not handling different form factors well. Let\u2019s use Tailwind CSS to add a responsive layout.<\/p>\n\n<h2 id=\"use-tailwind-css-to-build-a-responsive-layout\">Use Tailwind CSS to build a responsive layout<\/h2>\n\n<p>Tailwind makes delivering cool-looking websites much faster than writing our CSS manually. We\u2019ll load Tailwind via CDN for our demonstration purposes.<\/p>\n\n<p>Add the CDN to your CSP allowlist:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>https:\/\/cdn.jsdelivr.net\n<\/code><\/pre><\/div><\/div>\n\n<p>Navigate to <strong>Page Design<\/strong>, then <strong>Edit<\/strong> the page. Add the script to load the Tailwind resources in the <code class=\"language-plaintext highlighter-rouge\">&lt;head&gt;<\/code>. I added it after the <code class=\"language-plaintext highlighter-rouge\">&lt;style&gt;&lt;\/style&gt;<\/code> definitions before the <code class=\"language-plaintext highlighter-rouge\">&lt;\/head&gt;<\/code>.<\/p>\n\n<div class=\"language-html highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nt\">&lt;script <\/span><span class=\"na\">src=<\/span><span class=\"s\">\"https:\/\/cdn.jsdelivr.net\/npm\/@tailwindcss\/browser@4\"<\/span> <span class=\"na\">nonce=<\/span><span class=\"s\">\"{{nonceValue}}\"<\/span><span class=\"nt\">&gt;&lt;\/script&gt;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Loading external resources, like styles and scripts, requires a CSP nonce to mitigate cross-site scripting (XSS). You can read more about the CSP nonce on the <a href=\"https:\/\/content-security-policy.com\/nonce\/\">CSP Quick Reference Guide<\/a>.<\/p>\n\n<blockquote>\n  <p><strong>Note<\/strong><\/p>\n\n  <p>Don\u2019t use Tailwind from NPM CDN for production use cases. The Tailwind documentation notes this is for experimentation and prototyping only, as the CDN has rate limits. If your brand uses Tailwind for other production sites, you\u2019ve most likely defined custom mixins and themes in Tailwind. Therefore, reference your production Tailwind resources in place of the CDN we\u2019re using in this post.<\/p>\n<\/blockquote>\n\n<p>Remove the styles for <code class=\"language-plaintext highlighter-rouge\">#okta-auth-container<\/code> and <code class=\"language-plaintext highlighter-rouge\">#okta-login-container<\/code> from the <code class=\"language-plaintext highlighter-rouge\">&lt;style&gt;&lt;\/style&gt;<\/code> section. We can use Tailwind to handle it. The <code class=\"language-plaintext highlighter-rouge\">&lt;style&gt;&lt;\/style&gt;<\/code> section should only contain the CSS custom properties defined in <code class=\"language-plaintext highlighter-rouge\">:root<\/code> and the directive to use SIW Gen3.<\/p>\n\n<p>Add the styles for Tailwind. We\u2019ll add the classes to show the login container without the hero image in smaller form factors, then display the hero image with different widths depending on the breakpoints.<\/p>\n\n<p>The two <code class=\"language-plaintext highlighter-rouge\">div<\/code> containers look like this:<\/p>\n\n<div class=\"language-html highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nt\">&lt;div<\/span> <span class=\"na\">id=<\/span><span class=\"s\">\"okta-auth-container\"<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"h-screen flex bg-(--color-gray) bg-[{{bgImageUrl}}]\"<\/span><span class=\"nt\">&gt;<\/span>\n  <span class=\"nt\">&lt;div<\/span> <span class=\"na\">id=<\/span><span class=\"s\">\"okta-login-container\"<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"w-full min-w-sm lg:w-2\/3 xl:w-1\/2 bg-(image:--color-gradient) lg:bg-none bg-(--color-white) flex justify-center items-center\"<\/span><span class=\"nt\">&gt;&lt;\/div&gt;<\/span>\n<span class=\"nt\">&lt;\/div&gt;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Save the file and publish the changes. Feel free to test it out!<\/p>\n\n<h2 id=\"use-tailwind-for-custom-html-elements-on-your-okta-hosted-sign-in-page\">Use Tailwind for custom HTML elements on your Okta-hosted sign-in page<\/h2>\n\n<p>Tailwind excels at adding styled HTML elements to websites. We can also take advantage of this. Let\u2019s say you want to maintain continuity of the webpage from your site through the sign-in page by adding a footer with links to your brand\u2019s sites. Adding this new section involves changing the HTML node structure and styling the elements.<\/p>\n\n<p>We want a footer pinned to the bottom of the view, so we\u2019ll need a new parent container with vertical stacking and ensure the height of the footer stays consistent. Replace the HTML node structure to look like this:<\/p>\n\n<div class=\"language-html highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nt\">&lt;div<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"flex flex-col min-h-screen\"<\/span><span class=\"nt\">&gt;<\/span>        \n  <span class=\"nt\">&lt;div<\/span> <span class=\"na\">id=<\/span><span class=\"s\">\"okta-auth-container\"<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"flex grow bg-(--color-gray) bg-[{{bgImageUrl}}]\"<\/span><span class=\"nt\">&gt;<\/span>\n    <span class=\"nt\">&lt;div<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"w-full min-w-sm lg:w-2\/3 xl:w-1\/2 bg-(image:--color-gradient) lg:bg-none bg-(--color-white) flex justify-center items-center\"<\/span><span class=\"nt\">&gt;<\/span>\n        <span class=\"nt\">&lt;div<\/span> <span class=\"na\">id=<\/span><span class=\"s\">\"okta-login-container\"<\/span><span class=\"nt\">&gt;&lt;\/div&gt;<\/span>\n    <span class=\"nt\">&lt;\/div&gt;<\/span>\n  <span class=\"nt\">&lt;\/div&gt;<\/span>\n  <span class=\"nt\">&lt;footer<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"font-(family-name:--font-body)\"<\/span><span class=\"nt\">&gt;<\/span>\n    <span class=\"nt\">&lt;ul<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"h-12 flex justify-evenly items-center text-(--color-azul)\"<\/span><span class=\"nt\">&gt;<\/span>\n      <span class=\"nt\">&lt;li&gt;&lt;a<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"hover:text-(--color-orange) hover:underline\"<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"https:\/\/developer.okta.com\"<\/span><span class=\"nt\">&gt;<\/span>Terms<span class=\"nt\">&lt;\/a&gt;&lt;\/li&gt;<\/span>\n      <span class=\"nt\">&lt;li&gt;&lt;a<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"hover:text-(--color-orange) hover:underline\"<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"https:\/\/developer.okta.com\"<\/span><span class=\"nt\">&gt;<\/span>Docs<span class=\"nt\">&lt;\/a&gt;&lt;\/li&gt;<\/span>\n      <span class=\"nt\">&lt;li&gt;&lt;a<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"hover:text-(--color-orange) hover:underline\"<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"https:\/\/developer.okta.com\/blog\"<\/span><span class=\"nt\">&gt;<\/span>Blog<span class=\"nt\">&lt;\/a&gt;&lt;\/li&gt;<\/span>\n      <span class=\"nt\">&lt;li&gt;&lt;a<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"hover:text-(--color-orange) hover:underline\"<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"https:\/\/devforum.okta.com\"<\/span><span class=\"nt\">&gt;<\/span>Community<span class=\"nt\">&lt;\/a&gt;&lt;\/li&gt;<\/span>\n    <span class=\"nt\">&lt;\/ul&gt;<\/span>\n  <span class=\"nt\">&lt;\/footer&gt;<\/span>\n<span class=\"nt\">&lt;\/div&gt;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Everything redirects to the Okta Developer sites. \ud83d\ude0a I also maintained the style of font, text colors, and text decoration styles to match the SIW elements. CSS custom properties make consistency manageable.<\/p>\n\n<p>Feel free to save and publish to check it out!<\/p>\n\n<h2 id=\"add-custom-interactivity-on-the-okta-hosted-sign-in-page-using-an-external-library\">Add custom interactivity on the Okta-hosted sign-in page using an external library<\/h2>\n\n<p>Tailwind is great at styling HTML elements, but it\u2019s not a JavaScript library. If we want interactive elements on the sign-in page, we must rely on Web APIs or libraries to assist us. Let\u2019s say we want to ensure that users who sign in to the to-do app agree to the terms and conditions. We want a modal that blocks interaction with the SIW until the user agrees.<\/p>\n\n<p>We\u2019ll use Alpine for the heavy lifting because it\u2019s a lightweight JavaScript library that suits this need. We add the library via the NPM CDN, as we have already allowed the domain in our CSP. Add the following to the <code class=\"language-plaintext highlighter-rouge\">&lt;head&gt;&lt;\/head&gt;<\/code> section of the HTML. I added mine directly after the Tailwind script.<\/p>\n\n<div class=\"language-html highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nt\">&lt;script <\/span><span class=\"na\">defer<\/span> <span class=\"na\">src=<\/span><span class=\"s\">\"https:\/\/cdn.jsdelivr.net\/npm\/alpinejs@3.x.x\/dist\/cdn.min.js\"<\/span> <span class=\"na\">nonce=<\/span><span class=\"s\">\"{{nonceValue}}\"<\/span><span class=\"nt\">&gt;&lt;\/script&gt;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<blockquote>\n  <p><strong>Note<\/strong><\/p>\n\n  <p>We\u2019re including Alpine from the NPM CDN for demonstration and experimentation. For production applications, use a CDN that supports production scale. The NPM CDN applies rate limiting to prevent production-grade use.<\/p>\n<\/blockquote>\n\n<p>Next, we add the HTML tags to support the modal. Replace the HTML node structure to look like this:<\/p>\n\n<div class=\"language-html highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nt\">&lt;div<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"flex flex-col min-h-screen\"<\/span><span class=\"nt\">&gt;<\/span>\n  <span class=\"nt\">&lt;div<\/span> <span class=\"na\">id=<\/span><span class=\"s\">\"modal\"<\/span>\n    <span class=\"na\">x-data<\/span>\n    <span class=\"na\">x-cloak<\/span>\n    <span class=\"na\">x-show=<\/span><span class=\"s\">\"$store.modal.open\"<\/span> \n    <span class=\"na\">x-transition:enter=<\/span><span class=\"s\">\"transition ease-out duration-300\"<\/span>\n    <span class=\"na\">x-transition:enter-start=<\/span><span class=\"s\">\"opacity-0\"<\/span>\n    <span class=\"na\">x-transition:enter-end=<\/span><span class=\"s\">\"opacity-100\"<\/span>\n    <span class=\"na\">x-transition:leave=<\/span><span class=\"s\">\"transition ease-in duration-200\"<\/span>\n    <span class=\"na\">x-transition:leave-start=<\/span><span class=\"s\">\"opacity-100\"<\/span>\n    <span class=\"na\">x-transition:leave-end=<\/span><span class=\"s\">\"opacity-0 hidden\"<\/span>\n    <span class=\"na\">class=<\/span><span class=\"s\">\"fixed inset-0 z-50 flex items-center justify-center bg-(--color-black)\/80 bg-opacity-50\"<\/span><span class=\"nt\">&gt;<\/span>\n    <span class=\"nt\">&lt;div<\/span> <span class=\"na\">x-transition:enter=<\/span><span class=\"s\">\"transition ease-out duration-300\"<\/span>\n         <span class=\"na\">x-transition:enter-start=<\/span><span class=\"s\">\"opacity-0 scale-90\"<\/span>\n         <span class=\"na\">x-transition:enter-end=<\/span><span class=\"s\">\"opacity-100 scale-100\"<\/span>\n         <span class=\"na\">x-transition:leave=<\/span><span class=\"s\">\"transition ease-in duration-200\"<\/span>\n         <span class=\"na\">x-transition:leave-start=<\/span><span class=\"s\">\"opacity-100 scale-100\"<\/span>\n         <span class=\"na\">x-transition:leave-end=<\/span><span class=\"s\">\"opacity-0 scale-90\"<\/span>\n         <span class=\"na\">class=<\/span><span class=\"s\">\"bg-(--color-white) rounded-(--border-radius) shadow-lg p-8 max-w-md w-full mx-4\"<\/span><span class=\"nt\">&gt;<\/span>\n      <span class=\"nt\">&lt;h2<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"text-2xl font-(family-name:--font-header) text-(--color-black) mb-4 text-center\"<\/span><span class=\"nt\">&gt;<\/span>Welcome to to-do app<span class=\"nt\">&lt;\/h2&gt;<\/span>\n      <span class=\"nt\">&lt;p<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"text-(--color-black) mb-6\"<\/span><span class=\"nt\">&gt;<\/span>This app is in beta. Thank you for agreeing to our terms and conditions.<span class=\"nt\">&lt;\/p&gt;<\/span>\n      <span class=\"nt\">&lt;button<\/span> <span class=\"err\">@<\/span><span class=\"na\">click=<\/span><span class=\"s\">\"$store.modal.hide()\"<\/span> \n              <span class=\"na\">class=<\/span><span class=\"s\">\"w-full bg-(--color-fuchsia) hover:bg-(--color-orange) text-(--color-bright-white) font-medium py-2 px-4 rounded-(--border-radius) transition duration-200\"<\/span><span class=\"nt\">&gt;<\/span>\n          Agree\n      <span class=\"nt\">&lt;\/button&gt;<\/span>\n    <span class=\"nt\">&lt;\/div&gt;<\/span>\n  <span class=\"nt\">&lt;\/div&gt;<\/span>        \n  <span class=\"nt\">&lt;div<\/span> <span class=\"na\">id=<\/span><span class=\"s\">\"okta-auth-container\"<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"flex grow bg-(--color-gray) bg-[{{bgImageUrl}}]\"<\/span><span class=\"nt\">&gt;<\/span>\n    <span class=\"nt\">&lt;div<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"w-full min-w-sm lg:w-2\/3 xl:w-1\/2 bg-(image:--color-gradient) lg:bg-none bg-(--color-white) flex justify-center items-center\"<\/span><span class=\"nt\">&gt;<\/span>\n      <span class=\"nt\">&lt;div<\/span> <span class=\"na\">id=<\/span><span class=\"s\">\"okta-login-container\"<\/span><span class=\"nt\">&gt;&lt;\/div&gt;<\/span>\n    <span class=\"nt\">&lt;\/div&gt;<\/span>\n  <span class=\"nt\">&lt;\/div&gt;<\/span>\n  <span class=\"nt\">&lt;footer<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"font-(family-name:--font-body)\"<\/span><span class=\"nt\">&gt;<\/span>\n    <span class=\"nt\">&lt;ul<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"h-12 flex justify-evenly items-center text-(--color-azul)\"<\/span><span class=\"nt\">&gt;<\/span>\n      <span class=\"nt\">&lt;li&gt;&lt;a<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"hover:text-(--color-orange) hover:underline\"<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"https:\/\/developer.okta.com\"<\/span><span class=\"nt\">&gt;<\/span>Terms<span class=\"nt\">&lt;\/a&gt;&lt;\/li&gt;<\/span>\n      <span class=\"nt\">&lt;li&gt;&lt;a<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"hover:text-(--color-orange) hover:underline\"<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"https:\/\/developer.okta.com\"<\/span><span class=\"nt\">&gt;<\/span>Docs<span class=\"nt\">&lt;\/a&gt;&lt;\/li&gt;<\/span>\n      <span class=\"nt\">&lt;li&gt;&lt;a<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"hover:text-(--color-orange) hover:underline\"<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"https:\/\/developer.okta.com\/blog\"<\/span><span class=\"nt\">&gt;<\/span>Blog<span class=\"nt\">&lt;\/a&gt;&lt;\/li&gt;<\/span>\n      <span class=\"nt\">&lt;li&gt;&lt;a<\/span> <span class=\"na\">class=<\/span><span class=\"s\">\"hover:text-(--color-orange) hover:underline\"<\/span> <span class=\"na\">href=<\/span><span class=\"s\">\"https:\/\/devforum.okta.com\"<\/span><span class=\"nt\">&gt;<\/span>Community<span class=\"nt\">&lt;\/a&gt;&lt;\/li&gt;<\/span>\n    <span class=\"nt\">&lt;\/ul&gt;<\/span>\n  <span class=\"nt\">&lt;\/footer&gt;<\/span>\n<span class=\"nt\">&lt;\/div&gt;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>It\u2019s a lot to add, but I want the smooth transition animations. \ud83d\ude05 The built-in enter and leave states make adding the transition animation so much easier than doing it manually.<\/p>\n\n<p>Notice we\u2019re using a state value to determine whether to show the modal. We\u2019re using global state management, and setting it up is the next step. We\u2019ll add initializing the state when Alpine initializes. Find the comment <code class=\"language-plaintext highlighter-rouge\">\/\/ Render the Okta Sign-In Widget<\/code> within the <code class=\"language-plaintext highlighter-rouge\">&lt;script&gt;&lt;\/script&gt;<\/code> section, and add the following code that runs after Alpine initializes:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">document<\/span><span class=\"p\">.<\/span><span class=\"nx\">addEventListener<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">alpine:init<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"p\">()<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">Alpine<\/span><span class=\"p\">.<\/span><span class=\"nx\">store<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">modal<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">open<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">show<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">this<\/span><span class=\"p\">.<\/span><span class=\"nx\">open<\/span> <span class=\"o\">=<\/span> <span class=\"kc\">true<\/span><span class=\"p\">;<\/span>\n    <span class=\"p\">},<\/span>\n    <span class=\"nx\">hide<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">this<\/span><span class=\"p\">.<\/span><span class=\"nx\">open<\/span> <span class=\"o\">=<\/span> <span class=\"kc\">false<\/span><span class=\"p\">;<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">});<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The event listener watches for the <code class=\"language-plaintext highlighter-rouge\">alpine:init<\/code> event and runs a function that defines an element in Alpine\u2019s store, <code class=\"language-plaintext highlighter-rouge\">modal<\/code>. The <code class=\"language-plaintext highlighter-rouge\">modal<\/code> store contains a property to track whether it\u2019s open and some helper methods for showing and hiding.<\/p>\n\n<p>When you save and publish, you\u2019ll see the modal upon site reload!<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/okta-custom-sign-in-page\/modal-siw-2379ac6fcdef9e9e55634f4f98d033535efcbf959ab46ad793c1e3be94a69c84.jpg\" alt=\"A modal which displays on top of the sign-in page where the user must accept terms before continuing\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>We made the modal fixed even if the user presses <kbd>Esc<\/kbd> or selects the scrim. Users must agree to the terms to continue.<\/p>\n\n<h2 id=\"customize-okta-hosted-sign-in-page-behavior-using-web-apis\">Customize Okta-hosted sign-in page behavior using Web APIs<\/h2>\n\n<p>We display the modal as soon as the webpage loads. It works, but we can also display the modal after the Sign-In Widget renders. Doing so allows us to use the nice enter and leave CSS transitions Alpine supports. We want to watch for changes to the DOM within the <code class=\"language-plaintext highlighter-rouge\">&lt;div id=\"okta-login-container\"&gt;&lt;\/div&gt;<\/code>. This is the parent container that renders the SIW. We can use the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/MutationObserver\"><code class=\"language-plaintext highlighter-rouge\">MutationObserver<\/code> Web API<\/a> and watch for DOM mutations within the <code class=\"language-plaintext highlighter-rouge\">div<\/code>.<\/p>\n\n<p>In the <code class=\"language-plaintext highlighter-rouge\">&lt;script&gt;&lt;\/script&gt;<\/code> section, after the event listener for <code class=\"language-plaintext highlighter-rouge\">alpine:init<\/code>, add the following code:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">const<\/span> <span class=\"nx\">loginContainer<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">document<\/span><span class=\"p\">.<\/span><span class=\"nx\">querySelector<\/span><span class=\"p\">(<\/span><span class=\"dl\">\"<\/span><span class=\"s2\">#okta-login-container<\/span><span class=\"dl\">\"<\/span><span class=\"p\">);<\/span>\n\n<span class=\"c1\">\/\/ Use MutationObserver to watch for auth container element<\/span>\n<span class=\"kd\">const<\/span> <span class=\"nx\">mutationObserver<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nx\">MutationObserver<\/span><span class=\"p\">(()<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">const<\/span> <span class=\"nx\">element<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">loginContainer<\/span><span class=\"p\">.<\/span><span class=\"nx\">querySelector<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">[data-se*=\"auth-container\"]<\/span><span class=\"dl\">'<\/span><span class=\"p\">);<\/span>\n  <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"nx\">element<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nb\">document<\/span><span class=\"p\">.<\/span><span class=\"nx\">getElementById<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">modal<\/span><span class=\"dl\">'<\/span><span class=\"p\">).<\/span><span class=\"nx\">classList<\/span><span class=\"p\">.<\/span><span class=\"nx\">remove<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">hidden<\/span><span class=\"dl\">'<\/span><span class=\"p\">);<\/span>\n    <span class=\"c1\">\/\/ Open modal using Alpine store<\/span>\n    <span class=\"nx\">Alpine<\/span><span class=\"p\">.<\/span><span class=\"nx\">store<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">modal<\/span><span class=\"dl\">'<\/span><span class=\"p\">).<\/span><span class=\"nx\">show<\/span><span class=\"p\">();<\/span>\n    <span class=\"c1\">\/\/ Clean up the observer<\/span>\n    <span class=\"nx\">mutationObserver<\/span><span class=\"p\">.<\/span><span class=\"nx\">disconnect<\/span><span class=\"p\">();<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">});<\/span>\n\n<span class=\"nx\">mutationObserver<\/span><span class=\"p\">.<\/span><span class=\"nx\">observe<\/span><span class=\"p\">(<\/span><span class=\"nx\">loginContainer<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span>\n  <span class=\"na\">childList<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span><span class=\"p\">,<\/span>\n  <span class=\"na\">subtree<\/span><span class=\"p\">:<\/span> <span class=\"kc\">true<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Let\u2019s walk through what the code does. First, we\u2019re creating a variable to reference the parent container for the SIW, as we\u2019ll use it as the root element to target our work. Mutation observers can negatively impact performance, so it\u2019s essential to limit the scope of the observer as much as possible.<\/p>\n\n<p><strong>Create the observer<\/strong><\/p>\n\n<p>We create the observer and define the behavior for observation. The observer first looks for the element with the data attribute named <code class=\"language-plaintext highlighter-rouge\">se<\/code>, which includes the value <code class=\"language-plaintext highlighter-rouge\">auth-container<\/code>. Okta adds a node with the data attribute for internal operations. We\u2019ll do the same for our internal operations. \ud83d\ude0e<\/p>\n\n<p><strong>Define the behavior upon observation<\/strong><\/p>\n\n<p>Once we have an element matching the <code class=\"language-plaintext highlighter-rouge\">auth-container<\/code> data attribute, we show the modal, which triggers the enter transition animation. Then we clean up the observer.<\/p>\n\n<p><strong>Identify what to observe<\/strong><\/p>\n\n<p>We begin by observing the DOM and pass in the element to use as the root, along with a configuration specifying what to watch for. We want to look for changes in child elements and the subtree from the root to find the SIW elements.<\/p>\n\n<p>Lastly, let\u2019s enable the modal to trigger based on the observer. I intentionally provided you with code snippets that force the modal to display before the SIW renders, so you could take sneak peeks at your work as we went along.<\/p>\n\n<p>In the HTML node structure, find the <code class=\"language-plaintext highlighter-rouge\">&lt;div id=\"modal\"&gt;<\/code>. It\u2019s missing a class that hides the modal initially. Add the class <code class=\"language-plaintext highlighter-rouge\">hidden<\/code> to the class list. The class list for the <code class=\"language-plaintext highlighter-rouge\">&lt;div&gt;<\/code> should look like<\/p>\n\n<div class=\"language-html highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nt\">&lt;div<\/span> <span class=\"na\">id=<\/span><span class=\"s\">\"modal\"<\/span>\n    <span class=\"na\">x-data<\/span>\n    <span class=\"na\">x-cloak<\/span>\n    <span class=\"na\">x-show=<\/span><span class=\"s\">\"$store.modal.open\"<\/span> \n    <span class=\"na\">x-transition:enter=<\/span><span class=\"s\">\"transition ease-out duration-300\"<\/span>\n    <span class=\"na\">x-transition:enter-start=<\/span><span class=\"s\">\"opacity-0\"<\/span>\n    <span class=\"na\">x-transition:enter-end=<\/span><span class=\"s\">\"opacity-100\"<\/span>\n    <span class=\"na\">x-transition:leave=<\/span><span class=\"s\">\"transition ease-in duration-200\"<\/span>\n    <span class=\"na\">x-transition:leave-start=<\/span><span class=\"s\">\"opacity-100\"<\/span>\n    <span class=\"na\">x-transition:leave-end=<\/span><span class=\"s\">\"opacity-0 hidden\"<\/span>\n    <span class=\"na\">class=<\/span><span class=\"s\">\"hidden fixed inset-0 z-50 flex items-center justify-center bg-(--color-black)\/80 bg-opacity-50\"<\/span><span class=\"nt\">&gt;<\/span>\n\n<span class=\"c\">&lt;!-- Remaining modal structure here. Compare your work to the class list above --&gt;<\/span>\n\n<span class=\"nt\">&lt;\/div&gt;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Then, in the <code class=\"language-plaintext highlighter-rouge\">alpine:init<\/code> event listener, change the modal\u2019s <code class=\"language-plaintext highlighter-rouge\">open<\/code> property to default to <code class=\"language-plaintext highlighter-rouge\">false<\/code>:<\/p>\n\n<div class=\"language-js highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">document<\/span><span class=\"p\">.<\/span><span class=\"nx\">addEventListener<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">alpine:init<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"p\">()<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n  <span class=\"nx\">Alpine<\/span><span class=\"p\">.<\/span><span class=\"nx\">store<\/span><span class=\"p\">(<\/span><span class=\"dl\">'<\/span><span class=\"s1\">modal<\/span><span class=\"dl\">'<\/span><span class=\"p\">,<\/span> <span class=\"p\">{<\/span>\n    <span class=\"na\">open<\/span><span class=\"p\">:<\/span> <span class=\"kc\">false<\/span><span class=\"p\">,<\/span>\n    <span class=\"nx\">show<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">this<\/span><span class=\"p\">.<\/span><span class=\"nx\">open<\/span> <span class=\"o\">=<\/span> <span class=\"kc\">true<\/span><span class=\"p\">;<\/span>\n    <span class=\"p\">},<\/span>\n    <span class=\"nx\">hide<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">this<\/span><span class=\"p\">.<\/span><span class=\"nx\">open<\/span> <span class=\"o\">=<\/span> <span class=\"kc\">false<\/span><span class=\"p\">;<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">});<\/span>\n<span class=\"p\">});<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Save and publish your changes. You\u2019ll now notice a slight delay before the modal eases into view. So smooth!<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/okta-custom-sign-in-page\/final-siw-desktop-211b475e04926250d77e2a72779c27ea2c22a65ad47f43322c22ba81006f5df2.jpg\" alt=\"A customized Okta-hosted Sign-In Widget with custom elements, colors, and styles\" width=\"800\" class=\"center-image\" \/><\/p>\n\n<p>It\u2019s worth noting that our solution isn\u2019t foolproof; a savvy user can hide the modal and continue interacting with the sign-in widget by manipulating elements in the browser\u2019s debugger. You\u2019ll need to add extra checks and more robust code for foolproof methods. Still, this example provides a general idea of capabilities and how one might approach adding interactive components to the sign-in experience.<\/p>\n\n<p>Don\u2019t forget to test any implementation changes to the sign-in page for accessibility. The default site and the sign-in widget are accessible. Any changes or customizations we make may alter the accessibility of the site.<\/p>\n\n<p>You can connect your brand to one of our sample apps to see it work end-to-end. Follow the instructions in the README of our <a href=\"https:\/\/github.com\/okta-samples\/okta-react-sample\">Okta React Sample<\/a> to run the app locally. You\u2019ll need to update your Okta OpenID Connect (OIDC) application to work with the domain. In the Okta Admin Console, navigate to <strong>Applications<\/strong> &gt; <strong>Applications<\/strong> and find the Okta application for your custom app. Navigate to the <strong>Sign On<\/strong> tab. You\u2019ll see a section for <strong>OpenID Connect ID Token<\/strong>. Select <strong>Edit<\/strong> and select <strong>Custom URL<\/strong> for your brand\u2019s sign-in URL as the <strong>Issuer<\/strong> value.<\/p>\n\n<p>You\u2019ll use the issuer value, which matches your brand\u2019s custom URL, and the Okta application\u2019s client ID in your custom app\u2019s OIDC configuration.<\/p>\n\n<h2 id=\"add-tailwind-web-apis-and-javascript-libraries-to-customize-your-okta-hosted-sign-in-page\">Add Tailwind, Web APIs, and JavaScript libraries to customize your Okta-hosted sign-in page<\/h2>\n\n<p>I hope you found this post interesting and unlocked the potential of how much you can customize the Okta-hosted Sign-In Widget experience.<\/p>\n\n<p>You can find the final code for this project in the <a href=\"https:\/\/github.com\/oktadev\/okta-js-siw-customization-example\/tree\/main\/custom-signin-blog-post\">GitHub repo<\/a>.<\/p>\n\n<p>If you liked this post, check out these resources.<\/p>\n\n<ul>\n  <li><a href=\"\/blog\/2025-11-12-custom-signin\">Stretch Your Imagination and Build a Delightful Sign-In Experience<\/a><\/li>\n  <li><a href=\"https:\/\/developer.okta.com\/docs\/concepts\/sign-in-widget\/\">The Okta Sign-In Widget<\/a><\/li>\n<\/ul>\n\n<p>Remember to follow us on <a href=\"https:\/\/www.linkedin.com\/company\/oktadev\">LinkedIn<\/a> and subscribe to our <a href=\"https:\/\/www.youtube.com\/c\/oktadev\">YouTube<\/a> for more exciting content. Let us know how you customized the Okta-hosted sign-in page. We\u2019d love to see what you came up with.<\/p>\n\n<p>We also want to hear from you about topics you want to see and questions you may have. Leave us a comment below!<\/p>\n\n","pubDate":"Mon, 24 Nov 2025 00:00:00 -0500","link":"https:\/\/developer.okta.com\/blog\/2025\/11\/24\/okta-custom-sign-in-page","guid":"https:\/\/developer.okta.com\/blog\/2025\/11\/24\/okta-custom-sign-in-page"},{"title":"Secure Authentication with a Push Notification in Your iOS Device","description":"<p>Building secure and seamless sign-in experiences is a core challenge for today\u2019s iOS developers. Users expect authentication that feels instant, yet protects them with strong safeguards like multi-factor authentication (MFA). With Okta\u2019s DirectAuth and push notification support, you can achieve both \u2013 delivering native, phishing-resistant MFA flows without ever leaving your app.<\/p>\n\n<p>In this post, we\u2019ll walk you through how to:<\/p>\n<ol>\n  <li>Set up your Okta developer account<\/li>\n  <li>Configure your Okta org for DirectAuth and push notification factor<\/li>\n  <li>Enable your iOS app to drive DirectAuth flows natively<\/li>\n  <li>Create an AuthService with the support of DirectAuth<\/li>\n  <li>Build a fully working SwiftUI demo leveraging the AuthService<\/li>\n<\/ol>\n\n<p>Note: This guide assumes you\u2019re comfortable developing in Xcode using Swift and have basic familiarity with Okta\u2019s identity flows.<\/p>\n\n<p>If you want to skip the tutorial and run the project, you can <a href=\"https:\/\/github.com\/oktadev\/okta-ios-swift-directauth-example\">follow the instructions in the project\u2019s README<\/a>.<\/p>\n\n<p><strong class=\"hide\">Table of Contents<\/strong><\/p>\n<ul id=\"markdown-toc\">\n  <li><a href=\"#use-okta-directauth-with-push-notification-factor\" id=\"markdown-toc-use-okta-directauth-with-push-notification-factor\">Use Okta DirectAuth with push notification factor<\/a><\/li>\n  <li><a href=\"#prefer-phishing-resistant-authentication-factors\" id=\"markdown-toc-prefer-phishing-resistant-authentication-factors\">Prefer phishing-resistant authentication factors<\/a><\/li>\n  <li><a href=\"#set-up-your-ios-project-with-oktas-mobile-sdks\" id=\"markdown-toc-set-up-your-ios-project-with-oktas-mobile-sdks\">Set up your iOS project with Okta\u2019s mobile SDKs<\/a><\/li>\n  <li><a href=\"#authenticate-your-ios-app-using-okta-directauth\" id=\"markdown-toc-authenticate-your-ios-app-using-okta-directauth\">Authenticate your iOS app using Okta DirectAuth<\/a><\/li>\n  <li><a href=\"#add-the-oidc-configuration-to-your-ios-app\" id=\"markdown-toc-add-the-oidc-configuration-to-your-ios-app\">Add the OIDC configuration to your iOS app<\/a><\/li>\n  <li><a href=\"#add-authentication-in-your-ios-app-without-a-browser-redirect-using-okta-directauth\" id=\"markdown-toc-add-authentication-in-your-ios-app-without-a-browser-redirect-using-okta-directauth\">Add authentication in your iOS app without a browser redirect using Okta DirectAuth<\/a>    <ul>\n      <li><a href=\"#secure-native-sign-in-in-ios\" id=\"markdown-toc-secure-native-sign-in-in-ios\">Secure, native sign-in in iOS<\/a><\/li>\n      <li><a href=\"#sign-out-users-when-using-directauth\" id=\"markdown-toc-sign-out-users-when-using-directauth\">Sign-out users when using DirectAuth<\/a><\/li>\n      <li><a href=\"#refresh-access-tokens-securely\" id=\"markdown-toc-refresh-access-tokens-securely\">Refresh access tokens securely<\/a><\/li>\n    <\/ul>\n  <\/li>\n  <li><a href=\"#display-the-authenticated-users-information\" id=\"markdown-toc-display-the-authenticated-users-information\">Display the authenticated user\u2019s information<\/a><\/li>\n  <li><a href=\"#build-the-swiftui-views-to-display-authenticated-state\" id=\"markdown-toc-build-the-swiftui-views-to-display-authenticated-state\">Build the SwiftUI views to display authenticated state<\/a>    <ul>\n      <li><a href=\"#read-id-token-info\" id=\"markdown-toc-read-id-token-info\">Read ID token info<\/a><\/li>\n    <\/ul>\n  <\/li>\n  <li><a href=\"#view-the-authenticated-users-profile-info\" id=\"markdown-toc-view-the-authenticated-users-profile-info\">View the authenticated user\u2019s profile info<\/a>    <ul>\n      <li><a href=\"#keeping-tokens-refreshed-and-maintaining-user-sessions\" id=\"markdown-toc-keeping-tokens-refreshed-and-maintaining-user-sessions\">Keeping tokens refreshed and maintaining user sessions<\/a><\/li>\n    <\/ul>\n  <\/li>\n  <li><a href=\"#build-your-own-secure-native-sign-in-ios-app\" id=\"markdown-toc-build-your-own-secure-native-sign-in-ios-app\">Build your own secure native sign-in iOS app<\/a><\/li>\n<\/ul>\n\n<h2 id=\"use-okta-directauth-with-push-notification-factor\">Use Okta DirectAuth with push notification factor<\/h2>\n\n<p>The first step in implementing Direct Authentication with push-based MFA is setting up your Okta org and enabling the Push Notification factor. DirectAuth allows your app to handle authentication entirely within its own native UI \u2013 no browser redirection required \u2013 while still leveraging Okta\u2019s secure OAuth 2.0 and OpenID Connect (OIDC) standards under the hood.<\/p>\n\n<p>This means your app can seamlessly verify credentials, obtain tokens, and trigger a push notification challenge without switching contexts or relying on the <code class=\"language-plaintext highlighter-rouge\">SafariViewController<\/code>.<\/p>\n\n<p>Before you begin, you\u2019ll need an Okta Integrator Free Plan account. To get one, sign up for an <a href=\"https:\/\/developer.okta.com\/login\">Integrator account<\/a>. Once you have an account, sign in to your <a href=\"https:\/\/developer.okta.com\/login\">Integrator account<\/a>. Next, in the Admin Console:<\/p>\n\n<ol>\n  <li>Go to <strong>Applications<\/strong> &gt; <strong>Applications<\/strong><\/li>\n  <li>Select <strong>Create App Integration<\/strong><\/li>\n  <li>Select <strong>OIDC - OpenID Connect<\/strong> as the sign-in method<\/li>\n  <li>Select <strong>Native Application<\/strong> as the application type, then select <strong>Next<\/strong><\/li>\n  <li>Enter an app integration name<\/li>\n  <li>Configure the redirect URIs:\n    <ul>\n      <li><strong>Redirect URI<\/strong>: <code class=\"language-plaintext highlighter-rouge\">com.okta.{yourOktaDomain}:\/callback<\/code><\/li>\n      <li><strong>Post Logout Redirect URI<\/strong>: <code class=\"language-plaintext highlighter-rouge\">com.okta.{yourOktaDomain}:\/<\/code> (where <code class=\"language-plaintext highlighter-rouge\">{yourOktaDomain}.okta.com<\/code> is your Okta domain name). Your domain name is reversed to provide a unique scheme to open your app on a device.<\/li>\n    <\/ul>\n  <\/li>\n  <li>Select <strong>Advanced v<\/strong>.\n    <ul>\n      <li>Select the <strong>OOB<\/strong> and <strong>MFA OOB<\/strong> grant types.<\/li>\n    <\/ul>\n  <\/li>\n  <li>In the <strong>Controlled access<\/strong> section, select the appropriate access level<\/li>\n  <li>Select <strong>Save<\/strong><\/li>\n<\/ol>\n\n<p>NOTE: When using a custom authorization server, you need to set up authorization policies. Complete these additional steps:<\/p>\n\n<ol>\n  <li>In the Admin Console, go to <strong>Security<\/strong> &gt; <strong>API<\/strong> &gt; <strong>Authorization Servers<\/strong><\/li>\n  <li>Select your custom authorization server (<code class=\"language-plaintext highlighter-rouge\">default<\/code>)<\/li>\n  <li>On the Access Policies tab, ensure you have at least one policy:\n    <ul>\n      <li>If no policies exist, select <strong>Add New Access Policy<\/strong><\/li>\n      <li>Give it a name like \u201cDefault Policy\u201d<\/li>\n      <li>Set <strong>Assign<\/strong> to \u201cAll clients\u201d<\/li>\n      <li>Click <strong>Create Policy<\/strong><\/li>\n    <\/ul>\n  <\/li>\n  <li>For your policy, ensure you have at least one rule:\n    <ul>\n      <li>Select <strong>Add Rule<\/strong> if no rules exist<\/li>\n      <li>Give it a name like \u201cDefault Rule\u201d<\/li>\n      <li>Set <strong>Grant type<\/strong> is to \u201cAuthorization Code\u201d<\/li>\n      <li>Select <strong>Advanced<\/strong> and enable \u201cMFA OOB\u201d<\/li>\n      <li>Set <strong>User is<\/strong> to \u201cAny user assigned the app\u201d<\/li>\n      <li>Set <strong>Scopes requested<\/strong> to \u201cAny scopes\u201d<\/li>\n      <li>Select <strong>Create Rule<\/strong><\/li>\n    <\/ul>\n  <\/li>\n<\/ol>\n\n<p>For more details, see the <a href=\"https:\/\/developer.okta.com\/docs\/concepts\/auth-servers\/#custom-authorization-server\">Custom Authorization Server<\/a> documentation.<\/p>\n\n<details>\n   <summary>Where are my new app's credentials?<\/summary>\n  <div>\n    <p>Creating an OIDC Native App manually in the Admin Console configures your Okta Org with the application settings.<\/p>\n\n    <p>After creating the app, you can find the configuration details on the app\u2019s <strong>General<\/strong> tab:<\/p>\n    <ul>\n      <li><strong>Client ID<\/strong>: Found in the <strong>Client Credentials<\/strong> section<\/li>\n      <li><strong>Issuer<\/strong>: Found in the <strong>Issuer URI<\/strong> field for the authorization server that appears by selecting <strong>Security<\/strong> &gt; <strong>API<\/strong> from the navigation pane.<\/li>\n    <\/ul>\n\n    <div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>  Issuer:    https:\/\/dev-133337.okta.com\/oauth2\/default\n  Client ID: 0oab8eb55Kb9jdMIr5d6\n<\/code><\/pre><\/div>    <\/div>\n\n    <p><strong>NOTE<\/strong>: You can also use the <a href=\"https:\/\/github.com\/okta\/okta-cli-client\">Okta CLI Client<\/a> or <a href=\"https:\/\/github.com\/okta\/okta-powershell-cli\">Okta PowerShell Module<\/a> to automate this process. See this guide for more information about setting up your app.<\/p>\n  <\/div>\n<\/details>\n\n<h2 id=\"prefer-phishing-resistant-authentication-factors\">Prefer phishing-resistant authentication factors<\/h2>\n\n<p>When implementing DirectAuth with push notifications, security remains your top priority. Every new Okta Integrator Free Plan account requires admins to configure multi-factor authentication (MFA) using Okta Verify by default. We\u2019ll keep these default settings for this tutorial, as they already support Okta Verify Push, the recommended factor for a native and secure authentication experience.<\/p>\n\n<p>Push notifications through Okta Verify provide strong, phishing-resistant protection by requiring the user to approve sign-in attempts directly from a trusted device. Combined with biometric verification (Face ID or Touch ID) or device PIN enforcement, Okta Verify Push ensures that only the legitimate user can complete the authentication flow \u2013 even if credentials are compromised.<\/p>\n\n<p>By default, push factor isn\u2019t enabled in the Integrator Free org. Let\u2019s enable it now.<\/p>\n\n<p>Navigate to <strong>Security<\/strong> &gt; <strong>Authenticators<\/strong>. Find <strong>Okta Verify<\/strong> and select <strong>Actions<\/strong> &gt; <strong>Edit<\/strong>. In the <strong>Okta Verify<\/strong> modal, find <strong>Verification options<\/strong> and select <strong>Push notification (Android and iOS only)<\/strong>. Select <strong>Save<\/strong>.<\/p>\n\n<h2 id=\"set-up-your-ios-project-with-oktas-mobile-sdks\">Set up your iOS project with Okta\u2019s mobile SDKs<\/h2>\n\n<p>Before integrating Okta DirectAuth and Push Notification MFA, make sure your development environment meets the following requirements:<\/p>\n\n<ul>\n  <li>Xcode 15.0 or later \u2013 This guide assumes you\u2019re comfortable developing iOS apps in Swift using Xcode.<\/li>\n  <li>Swift 5+ \u2013 All examples use modern Swift language features.<\/li>\n  <li>Swift Package Manager (SPM) \u2013 Dependency manager handled through SPM, which is built into Xcode.<\/li>\n<\/ul>\n\n<p>Once your environment is ready, create a new iOS project in Xcode and prepare it for integration with Okta\u2019s mobile libraries.<\/p>\n\n<h2 id=\"authenticate-your-ios-app-using-okta-directauth\">Authenticate your iOS app using Okta DirectAuth<\/h2>\n\n<p>If you are starting from scratch, create a new iOS app:<\/p>\n<ol>\n  <li>Open Xcode<\/li>\n  <li>Go to <strong>File<\/strong> &gt; <strong>New<\/strong> &gt; <strong>Project<\/strong><\/li>\n  <li>Select <strong>iOS App<\/strong> and select <strong>Next<\/strong><\/li>\n  <li>Enter the name of the project, such as \u201cokta-mfa-direct-auth\u201d<\/li>\n  <li>Set the Interface to SwiftUI<\/li>\n  <li>Select <strong>Next<\/strong> and save your project locally<\/li>\n<\/ol>\n\n<p>To integrate Okta\u2019s Direct Authentication SDK into your iOS app, we\u2019ll use Swift Package Manager (SPM) \u2013 the recommended and modern way to manage dependencies in Xcode.<\/p>\n\n<p>Follow these steps:<\/p>\n\n<ol>\n  <li>Open your project in Xcode (or create a new one if needed)<\/li>\n  <li>Go to <strong>File<\/strong> &gt; <strong>Add Package Dependencies<\/strong><\/li>\n  <li>In the search field at the top-right, enter: <code class=\"language-plaintext highlighter-rouge\">https:\/\/github.com\/okta\/okta-mobile-swift<\/code> and press <kbd>Return<\/kbd>. Xcode will automatically fetch the available packages.<\/li>\n  <li>Select the <strong>latest version<\/strong> (recommended) or specify a compatible version with your setup<\/li>\n  <li>When prompted to choose which products to add, ensure that you select your app target next to <strong>OktaDirectAuth<\/strong> and <strong>AuthFoundation<\/strong><\/li>\n  <li>Select <strong>Add Package<\/strong><\/li>\n<\/ol>\n\n<p>These packages provide all the tools you need to implement native authentication flows using OAuth 2.0 and OpenID Connect (OIDC) with DirectAuth, including secure token handling and MFA challenge management \u2013 without relying on a browser session.<\/p>\n\n<p>Once the integration is complete, you\u2019ll see <strong>OktaMobileSwift<\/strong> and its dependencies listed under your project\u2019s <strong>Package Dependencies<\/strong> section in Xcode.<\/p>\n\n<h2 id=\"add-the-oidc-configuration-to-your-ios-app\">Add the OIDC configuration to your iOS app<\/h2>\n\n<p>The cleanest and most scalable way to manage configuration is to use a property list file for Okta stored in your app bundle.<\/p>\n\n<p>Create the property list for your OIDC and app config by following these steps:<\/p>\n<ol>\n  <li>Right-click on the root folder of the project<\/li>\n  <li>Select <strong>New File from Template<\/strong> (<strong>New File<\/strong> in legacy Xcode versions)<\/li>\n  <li>Ensure you have iOS selected on the top picker<\/li>\n  <li>Select <strong>Property List template<\/strong> and select <strong>Next<\/strong><\/li>\n  <li>Name the template <code class=\"language-plaintext highlighter-rouge\">Okta<\/code> and select Create to create an <code class=\"language-plaintext highlighter-rouge\">Okta.plist<\/code> file<\/li>\n<\/ol>\n\n<p>You can edit the file in XML format by right-clicking and selecting <strong>Open As<\/strong> &gt; <strong>Source Code<\/strong>. Copy and paste the following code into the file.<\/p>\n\n<div class=\"language-xml highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cp\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;<\/span>\n<span class=\"cp\">&lt;!DOCTYPE plist PUBLIC \"-\/\/Apple\/\/DTD PLIST 1.0\/\/EN\" \"http:\/\/www.apple.com\/DTDs\/PropertyList-1.0.dtd\"&gt;<\/span>\n<span class=\"nt\">&lt;plist<\/span> <span class=\"na\">version=<\/span><span class=\"s\">\"1.0\"<\/span><span class=\"nt\">&gt;<\/span>\n<span class=\"nt\">&lt;dict&gt;<\/span>\n    <span class=\"nt\">&lt;key&gt;<\/span>scopes<span class=\"nt\">&lt;\/key&gt;<\/span>\n    <span class=\"nt\">&lt;string&gt;<\/span>openid profile offline_access<span class=\"nt\">&lt;\/string&gt;<\/span>\n    <span class=\"nt\">&lt;key&gt;<\/span>redirectUri<span class=\"nt\">&lt;\/key&gt;<\/span>\n    <span class=\"nt\">&lt;string&gt;<\/span>com.okta.{yourOktaDomain}:\/callback<span class=\"nt\">&lt;\/string&gt;<\/span>\n    <span class=\"nt\">&lt;key&gt;<\/span>clientId<span class=\"nt\">&lt;\/key&gt;<\/span>\n    <span class=\"nt\">&lt;string&gt;<\/span>{yourClientID}<span class=\"nt\">&lt;\/string&gt;<\/span>\n    <span class=\"nt\">&lt;key&gt;<\/span>issuer<span class=\"nt\">&lt;\/key&gt;<\/span>\n    <span class=\"nt\">&lt;string&gt;<\/span>{yourOktaDomain}\/oauth2\/default<span class=\"nt\">&lt;\/string&gt;<\/span>\n    <span class=\"nt\">&lt;key&gt;<\/span>logoutRedirectUri<span class=\"nt\">&lt;\/key&gt;<\/span>\n    <span class=\"nt\">&lt;string&gt;<\/span>com.okta.{yourOktaDomain}:\/<span class=\"nt\">&lt;\/string&gt;<\/span>\n<span class=\"nt\">&lt;\/dict&gt;<\/span>\n<span class=\"nt\">&lt;\/plist&gt;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Replace <code class=\"language-plaintext highlighter-rouge\">{yourOktaDomain}<\/code> and <code class=\"language-plaintext highlighter-rouge\">{yourClientID}<\/code> with the values from your Okta org.<\/p>\n\n<p>If you use something like this in your code, you can directly access the <code class=\"language-plaintext highlighter-rouge\">DirectAuth<\/code> shared instance, which is already initialized and ready to handle authentication requests.<\/p>\n\n<h2 id=\"add-authentication-in-your-ios-app-without-a-browser-redirect-using-okta-directauth\">Add authentication in your iOS app without a browser redirect using Okta DirectAuth<\/h2>\n\n<p>Now that you\u2019ve added the SDK and property list file, let\u2019s implement the main authentication logic for your app.<\/p>\n\n<p>We\u2019ll build a dedicated service called <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code>, responsible for logging users in and out, refreshing tokens, and managing session state.<\/p>\n\n<p>This service will rely on OktaDirectAuth for native authentication and <code class=\"language-plaintext highlighter-rouge\">AuthFoundation<\/code> for secure token handling.<\/p>\n\n<p>To set it up, create a new folder named <code class=\"language-plaintext highlighter-rouge\">Auth<\/code> under your project\u2019s folder structure, then add a new Swift file called <code class=\"language-plaintext highlighter-rouge\">AuthService.swift<\/code>.<\/p>\n\n<p>Here, you\u2019ll define your authentication protocol and a concrete class that integrates directly with the Okta SDK \u2013 making it easy to use across your SwiftUI or UIKit views.<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">import<\/span> <span class=\"kt\">AuthFoundation<\/span>\n<span class=\"kd\">import<\/span> <span class=\"kt\">OktaDirectAuth<\/span>\n<span class=\"kd\">import<\/span> <span class=\"kt\">Observation<\/span>\n<span class=\"kd\">import<\/span> <span class=\"kt\">Foundation<\/span>\n\n<span class=\"kd\">protocol<\/span> <span class=\"kt\">AuthServicing<\/span> <span class=\"p\">{<\/span>\n  <span class=\"c1\">\/\/ The accessToken of the logged in user<\/span>\n  <span class=\"k\">var<\/span> <span class=\"nv\">accessToken<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">?<\/span> <span class=\"p\">{<\/span> <span class=\"k\">get<\/span> <span class=\"p\">}<\/span>\n\n  <span class=\"c1\">\/\/ State for driving SwiftUI<\/span>\n  <span class=\"k\">var<\/span> <span class=\"nv\">state<\/span><span class=\"p\">:<\/span> <span class=\"kt\">AuthService<\/span><span class=\"o\">.<\/span><span class=\"kt\">State<\/span> <span class=\"p\">{<\/span> <span class=\"k\">get<\/span> <span class=\"p\">}<\/span>\n\n  <span class=\"c1\">\/\/ Sign in (Password + Okta Verify Push)<\/span>\n  <span class=\"kd\">func<\/span> <span class=\"nf\">signIn<\/span><span class=\"p\">(<\/span><span class=\"nv\">username<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">,<\/span> <span class=\"nv\">password<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">)<\/span> <span class=\"k\">async<\/span> <span class=\"k\">throws<\/span>\n\n  <span class=\"c1\">\/\/ Sign out &amp; revoke tokens<\/span>\n  <span class=\"kd\">func<\/span> <span class=\"nf\">signOut<\/span><span class=\"p\">()<\/span> <span class=\"k\">async<\/span>\n\n  <span class=\"c1\">\/\/ Refresh access token if possible (returns updated token if refreshed)<\/span>\n  <span class=\"kd\">func<\/span> <span class=\"nf\">refreshTokenIfNeeded<\/span><span class=\"p\">()<\/span> <span class=\"k\">async<\/span> <span class=\"k\">throws<\/span>\n\n  <span class=\"c1\">\/\/ Getting the userInfo out of the Credential<\/span>\n  <span class=\"kd\">func<\/span> <span class=\"nf\">userInfo<\/span><span class=\"p\">()<\/span> <span class=\"k\">async<\/span> <span class=\"k\">throws<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"kt\">UserInfo<\/span><span class=\"p\">?<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>With this added, you will get an error that <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code> can\u2019t be found. That\u2019s because we haven\u2019t created the class yet. Below this code, add the following declarations of the <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code> class:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">@Observable<\/span>\n<span class=\"kd\">final<\/span> <span class=\"kd\">class<\/span> <span class=\"kt\">AuthService<\/span><span class=\"p\">:<\/span> <span class=\"kt\">AuthServicing<\/span> <span class=\"p\">{<\/span>\n\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>After doing so, we next need to confirm the <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code> class to the <code class=\"language-plaintext highlighter-rouge\">AuthServicing<\/code> protocol and also create the <code class=\"language-plaintext highlighter-rouge\">State<\/code> enum, which will hold all the states of our Authentication process.<\/p>\n\n<p>To do that, first let\u2019s create the <code class=\"language-plaintext highlighter-rouge\">State<\/code> enum inside the <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code> class like this:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">@Observable<\/span>\n<span class=\"kd\">final<\/span> <span class=\"kd\">class<\/span> <span class=\"kt\">AuthService<\/span><span class=\"p\">:<\/span> <span class=\"kt\">AuthServicing<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">enum<\/span> <span class=\"kt\">State<\/span><span class=\"p\">:<\/span> <span class=\"kt\">Equatable<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">case<\/span> <span class=\"n\">idle<\/span>\n    <span class=\"k\">case<\/span> <span class=\"n\">authenticating<\/span>\n    <span class=\"k\">case<\/span> <span class=\"n\">waitingForPush<\/span>\n    <span class=\"k\">case<\/span> <span class=\"nf\">authorized<\/span><span class=\"p\">(<\/span><span class=\"kt\">Token<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">case<\/span> <span class=\"nf\">failed<\/span><span class=\"p\">(<\/span><span class=\"nv\">errorMessage<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">)<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The new code resolved the two errors about the <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code> and the <code class=\"language-plaintext highlighter-rouge\">State<\/code> enum. We only have one error to fix, which is confirming the class to the protocol.<\/p>\n\n<p>We will start implementing the functions top to bottom. Let\u2019s first add the two variables from the protocol, accessToken and state. After the definition of the enum, we will add the properties:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">@Observable<\/span>\n<span class=\"kd\">final<\/span> <span class=\"kd\">class<\/span> <span class=\"kt\">AuthService<\/span><span class=\"p\">:<\/span> <span class=\"kt\">AuthServicing<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kd\">enum<\/span> <span class=\"kt\">State<\/span><span class=\"p\">:<\/span> <span class=\"kt\">Equatable<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">case<\/span> <span class=\"n\">idle<\/span>\n    <span class=\"k\">case<\/span> <span class=\"n\">authenticating<\/span>\n    <span class=\"k\">case<\/span> <span class=\"n\">waitingForPush<\/span>\n    <span class=\"k\">case<\/span> <span class=\"nf\">authorized<\/span><span class=\"p\">(<\/span><span class=\"kt\">Token<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">case<\/span> <span class=\"nf\">failed<\/span><span class=\"p\">(<\/span><span class=\"nv\">errorMessage<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">)<\/span>\n  <span class=\"p\">}<\/span>\n\n  <span class=\"kd\">private(set)<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">state<\/span><span class=\"p\">:<\/span> <span class=\"kt\">State<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"n\">idle<\/span>\n\n  <span class=\"k\">var<\/span> <span class=\"nv\">accessToken<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">?<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"kc\">nil<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>For now, we will leave the <code class=\"language-plaintext highlighter-rouge\">accessToken<\/code> getter with a return value of <code class=\"language-plaintext highlighter-rouge\">nil<\/code>, as we are not using the token yet. We\u2019ll add the implementation later.<\/p>\n\n<p>Next, we\u2019ll add a private property to hold a reference to the <code class=\"language-plaintext highlighter-rouge\">DirectAuthenticationFlow<\/code> instance.<\/p>\n\n<p>This object manages the entire DirectAuth process, including credential verification, MFA challenges, and token issuance. The object must persist across authentication steps.<\/p>\n\n<p>Insert the following variable between the existing <code class=\"language-plaintext highlighter-rouge\">state<\/code> and <code class=\"language-plaintext highlighter-rouge\">accessToken<\/code> properties:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">private(set)<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">state<\/span><span class=\"p\">:<\/span> <span class=\"kt\">State<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"n\">idle<\/span>\n<span class=\"kd\">@ObservationIgnored<\/span> <span class=\"kd\">private<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">flow<\/span><span class=\"p\">:<\/span> <span class=\"kt\">DirectAuthenticationFlow<\/span><span class=\"p\">?<\/span>\n\n<span class=\"k\">var<\/span> <span class=\"nv\">accessToken<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">?<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">return<\/span> <span class=\"kc\">nil<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>To allocate the flow variable, we will need to implement an initializer for the <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code> class. Inside, we\u2019ll allocate the flow using the <code class=\"language-plaintext highlighter-rouge\">PropertyListConfiguration<\/code> that we introduced earlier. Just after the <code class=\"language-plaintext highlighter-rouge\">accessToken<\/code> getter, add the following function:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ MARK: Init<\/span>\n\n<span class=\"nf\">init<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n  <span class=\"c1\">\/\/ Prefer PropertyListConfiguration if Okta.plist exists; otherwise fall back<\/span>\n  <span class=\"k\">if<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">configuration<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span><span class=\"p\">?<\/span> <span class=\"kt\">OAuth2Client<\/span><span class=\"o\">.<\/span><span class=\"kt\">PropertyListConfiguration<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">self<\/span><span class=\"o\">.<\/span><span class=\"n\">flow<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span><span class=\"p\">?<\/span> <span class=\"kt\">DirectAuthenticationFlow<\/span><span class=\"p\">(<\/span><span class=\"nv\">client<\/span><span class=\"p\">:<\/span> <span class=\"kt\">OAuth2Client<\/span><span class=\"p\">(<\/span><span class=\"n\">configuration<\/span><span class=\"p\">))<\/span>\n  <span class=\"p\">}<\/span> <span class=\"k\">else<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">self<\/span><span class=\"o\">.<\/span><span class=\"n\">flow<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span><span class=\"p\">?<\/span> <span class=\"kt\">DirectAuthenticationFlow<\/span><span class=\"p\">()<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This will try to fetch the Okta.plist file from the project\u2019s folder, and if not found, will fall back to the default initializer of <code class=\"language-plaintext highlighter-rouge\">the DirectAuthenticationFlow<\/code>. We have now successfully allocated the <code class=\"language-plaintext highlighter-rouge\">DirectAuthenticationFlow<\/code>, and we can proceed with implementing the next functions of the protocol.<\/p>\n\n<p>Moving down to the first function in the protocol, which is the <code class=\"language-plaintext highlighter-rouge\">signIn(username: String, password: String)<\/code>.<\/p>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">signIn<\/code> method below performs the full authentication flow using Okta DirectAuth and Auth Foundation.\nIt authenticates a user with their username and password, handles MFA challenges (in this case, Okta Verify Push), and securely stores the resulting token for future API calls. Add the following code just under the Init that we just added.<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ MARK: AuthServicing<\/span>\n<span class=\"kd\">func<\/span> <span class=\"nf\">signIn<\/span><span class=\"p\">(<\/span><span class=\"nv\">username<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">,<\/span> <span class=\"nv\">password<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kt\">Task<\/span> <span class=\"p\">{<\/span> <span class=\"kd\">@MainActor<\/span> <span class=\"k\">in<\/span>\n    <span class=\"c1\">\/\/ 1\ufe0f\u20e3 Start the Sign-In Process<\/span>\n    <span class=\"c1\">\/\/ Update UI state and begin the DirectAuth flow with username\/password.<\/span>\n    <span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"n\">authenticating<\/span>\n    <span class=\"k\">do<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">let<\/span> <span class=\"nv\">result<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span> <span class=\"k\">await<\/span> <span class=\"n\">flow<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"nf\">start<\/span><span class=\"p\">(<\/span><span class=\"n\">username<\/span><span class=\"p\">,<\/span> <span class=\"nv\">with<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"nf\">password<\/span><span class=\"p\">(<\/span><span class=\"n\">password<\/span><span class=\"p\">))<\/span>\n\n      <span class=\"k\">switch<\/span> <span class=\"n\">result<\/span> <span class=\"p\">{<\/span>\n        <span class=\"c1\">\/\/ 2\ufe0f\u20e3 Handle Successful Authentication<\/span>\n        <span class=\"c1\">\/\/ Okta validated credentials, return access\/refresh\/ID tokens.<\/span>\n      <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"nf\">success<\/span><span class=\"p\">(<\/span><span class=\"k\">let<\/span> <span class=\"nv\">token<\/span><span class=\"p\">):<\/span>\n        <span class=\"k\">let<\/span> <span class=\"nv\">newCred<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"nf\">store<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span>\n        <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span> <span class=\"o\">=<\/span> <span class=\"n\">newCred<\/span>\n        <span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"nf\">authorized<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span>\n\n        <span class=\"c1\">\/\/ 3\ufe0f\u20e3 Handle MFA with Push Notification<\/span>\n        <span class=\"c1\">\/\/ Okta requires MFA, wait for push approval via Okta Verify.<\/span>\n      <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"nv\">mfaRequired<\/span><span class=\"p\">:<\/span>\n        <span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"n\">waitingForPush<\/span>\n        <span class=\"k\">let<\/span> <span class=\"nv\">status<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span> <span class=\"k\">await<\/span> <span class=\"n\">flow<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"nf\">resume<\/span><span class=\"p\">(<\/span><span class=\"nv\">with<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"nf\">oob<\/span><span class=\"p\">(<\/span><span class=\"nv\">channel<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"n\">push<\/span><span class=\"p\">))<\/span>\n        <span class=\"k\">if<\/span> <span class=\"k\">case<\/span> <span class=\"kd\">let<\/span> <span class=\"o\">.<\/span><span class=\"nf\">success<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span> <span class=\"o\">=<\/span> <span class=\"n\">status<\/span> <span class=\"p\">{<\/span>\n          <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"nf\">store<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span>\n          <span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"nf\">authorized<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span>\n        <span class=\"p\">}<\/span>\n      <span class=\"k\">default<\/span><span class=\"p\">:<\/span>\n        <span class=\"k\">break<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span> <span class=\"k\">catch<\/span> <span class=\"p\">{<\/span>\n      <span class=\"c1\">\/\/ 4\ufe0f\u20e3 Handle Errors Gracefully<\/span>\n      <span class=\"c1\">\/\/ Update state with a descriptive error message for the UI.<\/span>\n      <span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"nf\">failed<\/span><span class=\"p\">(<\/span><span class=\"nv\">errorMessage<\/span><span class=\"p\">:<\/span> <span class=\"n\">error<\/span><span class=\"o\">.<\/span><span class=\"n\">localizedDescription<\/span><span class=\"p\">)<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Let\u2019s break down what\u2019s happening step by step:<\/p>\n\n<p><strong>1. Start the sign-in process<\/strong><\/p>\n\n<p>When the function is called, it launches a new asynchronous Task and sets the UI state to .authenticating.\nIt then initiates the DirectAuth flow using the provided username and password:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">let<\/span> <span class=\"nv\">result<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span> <span class=\"k\">await<\/span> <span class=\"n\">flow<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"nf\">start<\/span><span class=\"p\">(<\/span><span class=\"n\">username<\/span><span class=\"p\">,<\/span> <span class=\"nv\">with<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"nf\">password<\/span><span class=\"p\">(<\/span><span class=\"n\">password<\/span><span class=\"p\">))<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This sends the user\u2019s credentials to Okta\u2019s Direct Authentication API and waits for a response.<\/p>\n\n<p><strong>2. Handle successful authentication<\/strong><\/p>\n\n<p>If Okta validates the credentials and no additional verification is needed, the result will be <code class=\"language-plaintext highlighter-rouge\">.success(token)<\/code>.<\/p>\n\n<p>The returned Token object contains access, refresh, and ID tokens.<\/p>\n\n<p>We securely persist the credentials using AuthFoundation:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">let<\/span> <span class=\"nv\">newCred<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"nf\">store<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span>\n<span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span> <span class=\"o\">=<\/span> <span class=\"n\">newCred<\/span>\n<span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"nf\">authorized<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This marks the user as authenticated and updates the app state, allowing your UI to transition to the signed-in experience.<\/p>\n\n<p><strong>3. Handle MFA with push notification<\/strong><\/p>\n\n<p>If Okta determines that an MFA challenge is required, the result will be .mfaRequired.\nThe app updates its state to .waitingForPush, prompting the user to approve the login on their Okta Verify app:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"n\">waitingForPush<\/span>\n<span class=\"k\">let<\/span> <span class=\"nv\">status<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span> <span class=\"k\">await<\/span> <span class=\"n\">flow<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"nf\">resume<\/span><span class=\"p\">(<\/span><span class=\"nv\">with<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"nf\">oob<\/span><span class=\"p\">(<\/span><span class=\"nv\">channel<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"n\">push<\/span><span class=\"p\">))<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">.oob(channel: .push)<\/code> parameter resumes the authentication flow by waiting for the push approval event from Okta Verify.<\/p>\n\n<p>Once the user approves, Okta returns a new token:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if<\/span> <span class=\"k\">case<\/span> <span class=\"kd\">let<\/span> <span class=\"o\">.<\/span><span class=\"nf\">success<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span> <span class=\"o\">=<\/span> <span class=\"n\">status<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"nf\">store<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"nf\">authorized<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p><strong>4. Handle errors<\/strong><\/p>\n\n<p>If any step fails (e.g., invalid credentials, network issues, or push timeout), the catch block updates the UI to show an error message:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"nf\">failed<\/span><span class=\"p\">(<\/span><span class=\"nv\">errorMessage<\/span><span class=\"p\">:<\/span> <span class=\"n\">error<\/span><span class=\"o\">.<\/span><span class=\"n\">localizedDescription<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The error function allows your app to display user-friendly error states while preserving robust error handling for debugging.<\/p>\n\n<h3 id=\"secure-native-sign-in-in-ios\">Secure, native sign-in in iOS<\/h3>\n\n<p>This function demonstrates a complete native sign-in experience with Okta DirectAuth, no web views, no redirects.<\/p>\n\n<p>It authenticates the user, manages token storage securely, and handles push-based MFA all within your app\u2019s Swift layer \u2013 making the authentication flow fast, secure, and frictionless.<\/p>\n\n<p>The following diagram illustrates how the authentication flow works under the hood when using Okta DirectAuth with push notification authentication factor:<\/p>\n\n<p><img src=\"\/assets-jekyll\/blog\/okta-ios-directauth\/diagram-25e524254ab609c95e5c606597336aec6c1d0f8cdb02af6cd085b813d2ab9356.svg\" alt=\"Flowchart showing the sequence of steps for authentication flow\" width=\"800\" \/><\/p>\n\n<h3 id=\"sign-out-users-when-using-directauth\">Sign-out users when using DirectAuth<\/h3>\n\n<p>Next from the protocol functions is the sign-out method. This method provides a clean and secure way to log the user out of the app.<\/p>\n\n<p>It revokes the user\u2019s active tokens from Okta and resets the local authentication state, ensuring that no stale credentials remain on the device. Add the following code right below the <code class=\"language-plaintext highlighter-rouge\">signIn<\/code> method:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">func<\/span> <span class=\"nf\">signOut<\/span><span class=\"p\">()<\/span> <span class=\"k\">async<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">if<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">credential<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">try<\/span><span class=\"p\">?<\/span> <span class=\"k\">await<\/span> <span class=\"n\">credential<\/span><span class=\"o\">.<\/span><span class=\"nf\">revoke<\/span><span class=\"p\">()<\/span>\n  <span class=\"p\">}<\/span>\n  <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span> <span class=\"o\">=<\/span> <span class=\"kc\">nil<\/span>\n  <span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"n\">idle<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Let\u2019s look at what each step does:\n<strong>1. Check for an existing credential<\/strong><\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">credential<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span> <span class=\"p\">{<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The method first checks if a stored credential (token) exists in memory.\n<code class=\"language-plaintext highlighter-rouge\">Credential.default<\/code> represents the current authenticated session created earlier during sign-in.<\/p>\n\n<p><strong>2. Revoke the tokens from Okta<\/strong><\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">try<\/span><span class=\"p\">?<\/span> <span class=\"k\">await<\/span> <span class=\"n\">credential<\/span><span class=\"o\">.<\/span><span class=\"nf\">revoke<\/span><span class=\"p\">()<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This line tells Okta to invalidate the access and refresh tokens associated with that credential.\nCalling <code class=\"language-plaintext highlighter-rouge\">revoke()<\/code> ensures that the user\u2019s session terminates locally and in the authorization server, preventing further API access with those tokens.<\/p>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">try?<\/code> operator is used to safely ignore any errors (e.g., network failure during logout), since token revocation is a best-effort operation.<\/p>\n\n<p><strong>3. Clear local credential data<\/strong><\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span> <span class=\"o\">=<\/span> <span class=\"kc\">nil<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>After revoking the tokens, the app clears the local credential object.<\/p>\n\n<p>This removes any sensitive authentication data from memory, ensuring that no valid tokens remain on the device.<\/p>\n\n<p><strong>4. Reset the authentication state<\/strong><\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"n\">idle<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Finally, the app updates its internal state back to <code class=\"language-plaintext highlighter-rouge\">.idle<\/code>, which tells the UI that the user is now logged out and ready to start a new session.<\/p>\n\n<p>You can use this state to trigger a transition back to the login screen or turn off authenticated features.<\/p>\n\n<p>The protocol confirmation is almost complete, and we only have two functions remaining to implement.<\/p>\n\n<h3 id=\"refresh-access-tokens-securely\">Refresh access tokens securely<\/h3>\n\n<p>Access tokens issued by Okta have a limited lifetime to reduce the risk of misuse if compromised. OAuth clients that can\u2019t maintain secrets, like mobile apps, require short access token lifetimes for security.<\/p>\n\n<p>To maintain a seamless user experience, your app should refresh tokens automatically before they expire.\nThe <code class=\"language-plaintext highlighter-rouge\">refreshTokenIfNeeded()<\/code> method handles this process securely using <code class=\"language-plaintext highlighter-rouge\">AuthFoundation<\/code>\u2019s built-in token management APIs.<\/p>\n\n<p>Let\u2019s walk through what it does. Add the following code right after the <code class=\"language-plaintext highlighter-rouge\">signOut<\/code> method:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">func<\/span> <span class=\"nf\">refreshTokenIfNeeded<\/span><span class=\"p\">()<\/span> <span class=\"k\">async<\/span> <span class=\"k\">throws<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">guard<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">credential<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span> <span class=\"k\">else<\/span> <span class=\"p\">{<\/span> <span class=\"k\">return<\/span> <span class=\"p\">}<\/span>\n  <span class=\"k\">try<\/span> <span class=\"k\">await<\/span> <span class=\"n\">credential<\/span><span class=\"o\">.<\/span><span class=\"nf\">refresh<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p><strong>1. Check for an existing credential<\/strong><\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">guard<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">credential<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span> <span class=\"k\">else<\/span> <span class=\"p\">{<\/span> <span class=\"k\">return<\/span> <span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Before attempting a token refresh, the method checks whether a valid credential exists.\nIf no credential is stored (e.g., the user hasn\u2019t signed in yet or has logged out), the method exits early.<\/p>\n\n<p><strong>2. Refresh the token<\/strong><\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">try<\/span> <span class=\"k\">await<\/span> <span class=\"n\">credential<\/span><span class=\"o\">.<\/span><span class=\"nf\">refresh<\/span><span class=\"p\">()<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This line tells Okta to exchange the refresh token for a new access token and ID token.<\/p>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">refresh()<\/code> method automatically updates the <code class=\"language-plaintext highlighter-rouge\">Credential<\/code> object with the new tokens and securely persists them using <code class=\"language-plaintext highlighter-rouge\">AuthFoundation<\/code>.<\/p>\n\n<p>If the refresh token has expired or is invalid, this call throws an error \u2013 allowing your app to detect the issue and prompt the user to sign in again.<\/p>\n\n<h2 id=\"display-the-authenticated-users-information\">Display the authenticated user\u2019s information<\/h2>\n\n<p>Lastly, let\u2019s look at the <code class=\"language-plaintext highlighter-rouge\">userInfo()<\/code> function. After authenticating, your app can access the user\u2019s profile information \u2013 such as their name, email, or user ID \u2013 from Okta using a standard OIDC endpoint.<\/p>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">userInfo()<\/code> method retrieves this data from the ID token or by calling the authorization server\u2019s <code class=\"language-plaintext highlighter-rouge\">\/userinfo<\/code> endpoint. The ID token doesn\u2019t necessarily include all of the profile information though, as the ID token is intentionally lightweight.<\/p>\n\n<p>Here\u2019s how it works. Add the following code after the end of <code class=\"language-plaintext highlighter-rouge\">refreshTokenIfNeeded()<\/code>:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">func<\/span> <span class=\"nf\">userInfo<\/span><span class=\"p\">()<\/span> <span class=\"k\">async<\/span> <span class=\"k\">throws<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"kt\">UserInfo<\/span><span class=\"p\">?<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">if<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">userInfo<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"n\">userInfo<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">userInfo<\/span>\n  <span class=\"p\">}<\/span> <span class=\"k\">else<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">do<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">guard<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">userInfo<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span> <span class=\"k\">await<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"nf\">userInfo<\/span><span class=\"p\">()<\/span> <span class=\"k\">else<\/span> <span class=\"p\">{<\/span>\n        <span class=\"k\">return<\/span> <span class=\"kc\">nil<\/span>\n      <span class=\"p\">}<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">userInfo<\/span>\n    <span class=\"p\">}<\/span> <span class=\"k\">catch<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"kc\">nil<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p><strong>1. Return the cached user info<\/strong><\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">if<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">userInfo<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"n\">userInfo<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">return<\/span> <span class=\"n\">userInfo<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>If the user\u2019s profile information has already been fetched and stored in memory, the method returns it immediately.<\/p>\n\n<p>This avoids unnecessary network calls, providing a fast and responsive experience.<\/p>\n\n<p><strong>2. Fetch user info<\/strong><\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">guard<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">userInfo<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span> <span class=\"k\">await<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"nf\">userInfo<\/span><span class=\"p\">()<\/span> <span class=\"k\">else<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">return<\/span> <span class=\"kc\">nil<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>If the cached data isn\u2019t available, the method fetches it directly from Okta using the <code class=\"language-plaintext highlighter-rouge\">UserInfo<\/code> endpoint.<\/p>\n\n<p>This endpoint returns standard OpenID Connect claims such as:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>sub (the user's unique ID)\nname\nemail\npreferred_username\netc...\n<\/code><\/pre><\/div><\/div>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">AuthFoundation<\/code> SDK handles the request and parsing for you, returning a <code class=\"language-plaintext highlighter-rouge\">UserInfo<\/code> object.<\/p>\n\n<p><strong>3. Handle errors gracefully<\/strong><\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">catch<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">return<\/span> <span class=\"kc\">nil<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>If the request fails (for example, due to a network issue or expired token), the function returns <code class=\"language-plaintext highlighter-rouge\">nil<\/code>.\nThis prevents your app from crashing and allows you to handle the error by displaying a default user state or prompting re-authentication.<\/p>\n\n<p>With this implemented, you\u2019ve resolved all the errors and should be able to build the app. \ud83c\udf89<\/p>\n\n<h2 id=\"build-the-swiftui-views-to-display-authenticated-state\">Build the SwiftUI views to display authenticated state<\/h2>\n\n<p>Now that we\u2019ve built the <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code> to handle sign-in, sign-out, token management, and user info retrieval, let\u2019s see how to integrate it into your app\u2019s UI.<\/p>\n\n<p>To maintain consistency in your architecture, rename the default <code class=\"language-plaintext highlighter-rouge\">ContentView<\/code> to <code class=\"language-plaintext highlighter-rouge\">AuthView<\/code> and update all references accordingly.<\/p>\n\n<p>This clarifies the purpose of the view \u2013 it will serve as the primary authentication interface.\nThen, create a <code class=\"language-plaintext highlighter-rouge\">Views<\/code> folder under your project\u2019s folder, drag and drop the <code class=\"language-plaintext highlighter-rouge\">AuthView<\/code> into the newly created folder, and create a new file named <code class=\"language-plaintext highlighter-rouge\">AuthViewModel.swift<\/code> in the same folder.<\/p>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">AuthViewModel<\/code> will encapsulate all authentication-related state and actions, acting as the communication layer between your view and the underlying <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code>.<\/p>\n\n<p>Add the following code in <code class=\"language-plaintext highlighter-rouge\">AuthViewModel.swift<\/code>:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">import<\/span> <span class=\"kt\">Foundation<\/span>\n<span class=\"kd\">import<\/span> <span class=\"kt\">Observation<\/span>\n<span class=\"kd\">import<\/span> <span class=\"kt\">AuthFoundation<\/span>\n\n<span class=\"c1\">\/\/\/ The `AuthViewModel` acts as the bridge between your app's UI and the authentication layer (`AuthService`).<\/span>\n<span class=\"c1\">\/\/\/ It coordinates user actions such as signing in, signing out, refreshing tokens, and fetching user profile data.<\/span>\n<span class=\"c1\">\/\/\/ This class uses Swift's `@Observable` macro so that your SwiftUI views can automatically react to state changes.<\/span>\n<span class=\"kd\">@Observable<\/span>\n<span class=\"kd\">final<\/span> <span class=\"kd\">class<\/span> <span class=\"kt\">AuthViewModel<\/span> <span class=\"p\">{<\/span>\n  <span class=\"c1\">\/\/ MARK: - Dependencies<\/span>\n\n  <span class=\"c1\">\/\/\/ The authentication service responsible for handling DirectAuth sign-in,<\/span>\n  <span class=\"c1\">\/\/\/ push-based MFA, token management, and user info retrieval.<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">authService<\/span><span class=\"p\">:<\/span> <span class=\"kt\">AuthServicing<\/span>\n\n  <span class=\"c1\">\/\/ MARK: - UI State Properties<\/span>\n\n  <span class=\"c1\">\/\/\/ Stores the user's token, which can be used for secure communication<\/span>\n  <span class=\"c1\">\/\/\/ with backend services that validate the user's identity.<\/span>\n  <span class=\"k\">var<\/span> <span class=\"nv\">accessToken<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">?<\/span>\n\n  <span class=\"c1\">\/\/\/ Represents a loading statex. Set to `true` when background operations are running<\/span>\n  <span class=\"c1\">\/\/\/ (such as sign-in, sign-out, or token refresh) to display a progress indicator.<\/span>\n  <span class=\"k\">var<\/span> <span class=\"nv\">isLoading<\/span><span class=\"p\">:<\/span> <span class=\"kt\">Bool<\/span> <span class=\"o\">=<\/span> <span class=\"kc\">false<\/span>\n\n  <span class=\"c1\">\/\/\/ Holds any human-readable error messages that should be displayed in the UI<\/span>\n  <span class=\"c1\">\/\/\/ (for example, invalid credentials or network errors).<\/span>\n  <span class=\"k\">var<\/span> <span class=\"nv\">errorMessage<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">?<\/span>\n\n  <span class=\"c1\">\/\/\/ The username and password properties are bound to text fields in the UI.<\/span>\n  <span class=\"c1\">\/\/\/ As the user types, these values update automatically thanks to SwiftUI's reactive data binding.<\/span>\n  <span class=\"c1\">\/\/\/ The view model then uses them to perform DirectAuth sign-in when the user submits the form.<\/span>\n  <span class=\"k\">var<\/span> <span class=\"nv\">username<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span> <span class=\"o\">=<\/span> <span class=\"s\">\"\"<\/span>\n  <span class=\"k\">var<\/span> <span class=\"nv\">password<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span> <span class=\"o\">=<\/span> <span class=\"s\">\"\"<\/span>\n\n  <span class=\"c1\">\/\/\/ Exposes the current authentication state (idle, authenticating, waitingForPush, authorized, failed)<\/span>\n  <span class=\"c1\">\/\/\/ as defined by the `AuthService.State` enum. The view can use this to display the correct UI.<\/span>\n  <span class=\"k\">var<\/span> <span class=\"nv\">state<\/span><span class=\"p\">:<\/span> <span class=\"kt\">AuthService<\/span><span class=\"o\">.<\/span><span class=\"kt\">State<\/span> <span class=\"p\">{<\/span>\n    <span class=\"n\">authService<\/span><span class=\"o\">.<\/span><span class=\"n\">state<\/span>\n  <span class=\"p\">}<\/span>\n\n  <span class=\"c1\">\/\/ MARK: - Initialization<\/span>\n\n  <span class=\"c1\">\/\/\/ Initializes the view model with a default instance of `AuthService`.<\/span>\n  <span class=\"c1\">\/\/\/ You can inject a mock `AuthServicing` implementation for testing.<\/span>\n  <span class=\"nf\">init<\/span><span class=\"p\">(<\/span><span class=\"nv\">authService<\/span><span class=\"p\">:<\/span> <span class=\"kt\">AuthServicing<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">AuthService<\/span><span class=\"p\">())<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">self<\/span><span class=\"o\">.<\/span><span class=\"n\">authService<\/span> <span class=\"o\">=<\/span> <span class=\"n\">authService<\/span>\n  <span class=\"p\">}<\/span>\n\n  <span class=\"c1\">\/\/ MARK: - Authentication Actions<\/span>\n\n  <span class=\"c1\">\/\/\/ Attempts to authenticate the user with the provided credentials.<\/span>\n  <span class=\"c1\">\/\/\/ This triggers the full DirectAuth flow -- including password verification,<\/span>\n  <span class=\"c1\">\/\/\/ push notification MFA (if required), and secure token storage via AuthFoundation.<\/span>\n  <span class=\"kd\">@MainActor<\/span>\n  <span class=\"kd\">func<\/span> <span class=\"nf\">signIn<\/span><span class=\"p\">()<\/span> <span class=\"k\">async<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nf\">setLoading<\/span><span class=\"p\">(<\/span><span class=\"kc\">true<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">defer<\/span> <span class=\"p\">{<\/span> <span class=\"nf\">setLoading<\/span><span class=\"p\">(<\/span><span class=\"kc\">false<\/span><span class=\"p\">)<\/span> <span class=\"p\">}<\/span>\n\n    <span class=\"k\">do<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">try<\/span> <span class=\"k\">await<\/span> <span class=\"n\">authService<\/span><span class=\"o\">.<\/span><span class=\"nf\">signIn<\/span><span class=\"p\">(<\/span><span class=\"nv\">username<\/span><span class=\"p\">:<\/span> <span class=\"n\">username<\/span><span class=\"p\">,<\/span> <span class=\"nv\">password<\/span><span class=\"p\">:<\/span> <span class=\"n\">password<\/span><span class=\"p\">)<\/span>\n      <span class=\"n\">accessToken<\/span> <span class=\"o\">=<\/span> <span class=\"n\">authService<\/span><span class=\"o\">.<\/span><span class=\"n\">accessToken<\/span>\n    <span class=\"p\">}<\/span> <span class=\"k\">catch<\/span> <span class=\"p\">{<\/span>\n      <span class=\"n\">errorMessage<\/span> <span class=\"o\">=<\/span> <span class=\"n\">error<\/span><span class=\"o\">.<\/span><span class=\"n\">localizedDescription<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">}<\/span>\n\n  <span class=\"c1\">\/\/\/ Signs the user out by revoking active tokens, clearing local credentials,<\/span>\n  <span class=\"c1\">\/\/\/ and resetting the app's authentication state.<\/span>\n  <span class=\"kd\">@MainActor<\/span>\n  <span class=\"kd\">func<\/span> <span class=\"nf\">signOut<\/span><span class=\"p\">()<\/span> <span class=\"k\">async<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nf\">setLoading<\/span><span class=\"p\">(<\/span><span class=\"kc\">true<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">defer<\/span> <span class=\"p\">{<\/span> <span class=\"nf\">setLoading<\/span><span class=\"p\">(<\/span><span class=\"kc\">false<\/span><span class=\"p\">)<\/span> <span class=\"p\">}<\/span>\n\n    <span class=\"k\">await<\/span> <span class=\"n\">authService<\/span><span class=\"o\">.<\/span><span class=\"nf\">signOut<\/span><span class=\"p\">()<\/span>\n  <span class=\"p\">}<\/span>\n\n  <span class=\"c1\">\/\/ MARK: - Token Handling<\/span>\n\n  <span class=\"c1\">\/\/\/ Refreshes the user's access token using their refresh token.<\/span>\n  <span class=\"c1\">\/\/\/ This allows the app to maintain a valid session without requiring<\/span>\n  <span class=\"c1\">\/\/\/ the user to log in again after the access token expires.<\/span>\n  <span class=\"kd\">@MainActor<\/span>\n  <span class=\"kd\">func<\/span> <span class=\"nf\">refreshToken<\/span><span class=\"p\">()<\/span> <span class=\"k\">async<\/span> <span class=\"p\">{<\/span>\n    <span class=\"nf\">setLoading<\/span><span class=\"p\">(<\/span><span class=\"kc\">true<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">defer<\/span> <span class=\"p\">{<\/span> <span class=\"nf\">setLoading<\/span><span class=\"p\">(<\/span><span class=\"kc\">false<\/span><span class=\"p\">)<\/span> <span class=\"p\">}<\/span>\n\n    <span class=\"k\">do<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">try<\/span> <span class=\"k\">await<\/span> <span class=\"n\">authService<\/span><span class=\"o\">.<\/span><span class=\"nf\">refreshTokenIfNeeded<\/span><span class=\"p\">()<\/span>\n      <span class=\"n\">accessToken<\/span> <span class=\"o\">=<\/span> <span class=\"n\">authService<\/span><span class=\"o\">.<\/span><span class=\"n\">accessToken<\/span>\n    <span class=\"p\">}<\/span> <span class=\"k\">catch<\/span> <span class=\"p\">{<\/span>\n      <span class=\"n\">errorMessage<\/span> <span class=\"o\">=<\/span> <span class=\"n\">error<\/span><span class=\"o\">.<\/span><span class=\"n\">localizedDescription<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">}<\/span>\n\n  <span class=\"c1\">\/\/ MARK: - User Info Retrieval<\/span>\n\n  <span class=\"c1\">\/\/\/ Fetches the authenticated user's profile information from Okta.<\/span>\n  <span class=\"c1\">\/\/\/ Returns a `UserInfo` object containing standard OIDC claims (such as `name`, `email`, and `sub`).<\/span>\n  <span class=\"c1\">\/\/\/ If fetching fails (e.g., due to expired tokens or network issues), it returns `nil`.<\/span>\n  <span class=\"kd\">@MainActor<\/span>\n  <span class=\"kd\">func<\/span> <span class=\"nf\">fetchUserInfo<\/span><span class=\"p\">()<\/span> <span class=\"k\">async<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"kt\">UserInfo<\/span><span class=\"p\">?<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">do<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">let<\/span> <span class=\"nv\">userInfo<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span> <span class=\"k\">await<\/span> <span class=\"n\">authService<\/span><span class=\"o\">.<\/span><span class=\"nf\">userInfo<\/span><span class=\"p\">()<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">userInfo<\/span>\n    <span class=\"p\">}<\/span> <span class=\"k\">catch<\/span> <span class=\"p\">{<\/span>\n      <span class=\"n\">errorMessage<\/span> <span class=\"o\">=<\/span> <span class=\"n\">error<\/span><span class=\"o\">.<\/span><span class=\"n\">localizedDescription<\/span>\n      <span class=\"k\">return<\/span> <span class=\"kc\">nil<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">}<\/span>\n\n  <span class=\"c1\">\/\/ MARK: - UI Helpers<\/span>\n\n  <span class=\"c1\">\/\/\/ Updates the `isLoading` property. This is used to show or hide<\/span>\n  <span class=\"c1\">\/\/\/ a loading spinner in your SwiftUI view while background work is in progress.<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"kd\">func<\/span> <span class=\"nf\">setLoading<\/span><span class=\"p\">(<\/span><span class=\"n\">_<\/span> <span class=\"nv\">value<\/span><span class=\"p\">:<\/span> <span class=\"kt\">Bool<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"n\">isLoading<\/span> <span class=\"o\">=<\/span> <span class=\"n\">value<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>With the view model in place, the next step is to bind it to your SwiftUI view.\nThe <code class=\"language-plaintext highlighter-rouge\">AuthView<\/code> will observe the <code class=\"language-plaintext highlighter-rouge\">AuthViewModel<\/code>, updating automatically as the authentication state changes.<\/p>\n\n<p>It will show the user\u2019s ID token when authenticated and provide controls for signing in, signing out, and refreshing the token.<\/p>\n\n<p>Open <code class=\"language-plaintext highlighter-rouge\">AuthView.swift<\/code>, remove the existing template code, and insert the following implementation:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">import<\/span> <span class=\"kt\">SwiftUI<\/span>\n<span class=\"kd\">import<\/span> <span class=\"kt\">AuthFoundation<\/span>\n\n<span class=\"c1\">\/\/\/ A simple wrapper for `UserInfo` used to present user profile data in a full-screen modal.<\/span>\n<span class=\"c1\">\/\/\/ Conforms to `Identifiable` so it can be used with `.fullScreenCover(item:)`.<\/span>\n<span class=\"kd\">struct<\/span> <span class=\"kt\">UserInfoModel<\/span><span class=\"p\">:<\/span> <span class=\"kt\">Identifiable<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">let<\/span> <span class=\"nv\">id<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">UUID<\/span><span class=\"p\">()<\/span>\n  <span class=\"k\">let<\/span> <span class=\"nv\">user<\/span><span class=\"p\">:<\/span> <span class=\"kt\">UserInfo<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/\/ The main SwiftUI view for managing the authentication experience.<\/span>\n<span class=\"c1\">\/\/\/ This view observes the `AuthViewModel`, displays different UI states<\/span>\n<span class=\"c1\">\/\/\/ based on the current authentication flow, and provides controls for<\/span>\n<span class=\"c1\">\/\/\/ signing in, signing out, refreshing tokens, and viewing user or token information.<\/span>\n<span class=\"kd\">struct<\/span> <span class=\"kt\">AuthView<\/span><span class=\"p\">:<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n\n  <span class=\"c1\">\/\/ MARK: - View Model<\/span>\n\n  <span class=\"c1\">\/\/\/ The view model that manages all authentication logic and state transitions.<\/span>\n  <span class=\"c1\">\/\/\/ It uses `@Observable` from Swift's Observation framework, so changes here<\/span>\n  <span class=\"c1\">\/\/\/ automatically trigger UI updates.<\/span>\n  <span class=\"kd\">@State<\/span> <span class=\"kd\">private<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">viewModel<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">AuthViewModel<\/span><span class=\"p\">()<\/span>\n\n  <span class=\"c1\">\/\/ MARK: - State and Presentation<\/span>\n\n  <span class=\"c1\">\/\/\/ Holds the currently fetched user information (if available).<\/span>\n  <span class=\"c1\">\/\/\/ When this value is set, the `UserInfoView` is displayed as a full-screen sheet.<\/span>\n  <span class=\"kd\">@State<\/span> <span class=\"kd\">private<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">userInfo<\/span><span class=\"p\">:<\/span> <span class=\"kt\">UserInfoModel<\/span><span class=\"p\">?<\/span>\n\n  <span class=\"c1\">\/\/\/ Controls whether the Token Info screen is presented as a full-screen modal.<\/span>\n  <span class=\"kd\">@State<\/span> <span class=\"kd\">private<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">showTokenInfo<\/span> <span class=\"o\">=<\/span> <span class=\"kc\">false<\/span>\n\n  <span class=\"c1\">\/\/ MARK: - View Body<\/span>\n\n  <span class=\"k\">var<\/span> <span class=\"nv\">body<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">VStack<\/span> <span class=\"p\">{<\/span>\n      <span class=\"c1\">\/\/ Render the UI based on the current authentication state.<\/span>\n      <span class=\"c1\">\/\/ Each case corresponds to a different phase of the DirectAuth flow.<\/span>\n      <span class=\"k\">switch<\/span> <span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"n\">state<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"n\">idle<\/span><span class=\"p\">,<\/span> <span class=\"o\">.<\/span><span class=\"nv\">failed<\/span><span class=\"p\">:<\/span>\n        <span class=\"n\">loginForm<\/span>\n      <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"nv\">authenticating<\/span><span class=\"p\">:<\/span>\n        <span class=\"kt\">ProgressView<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Signing in...\"<\/span><span class=\"p\">)<\/span>\n      <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"nv\">waitingForPush<\/span><span class=\"p\">:<\/span>\n        <span class=\"c1\">\/\/ Waiting for Okta Verify push approval<\/span>\n        <span class=\"kt\">WaitingForPushView<\/span> <span class=\"p\">{<\/span>\n          <span class=\"kt\">Task<\/span> <span class=\"p\">{<\/span> <span class=\"k\">await<\/span> <span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"nf\">signOut<\/span><span class=\"p\">()<\/span> <span class=\"p\">}<\/span>\n        <span class=\"p\">}<\/span>\n      <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"nv\">authorized<\/span><span class=\"p\">:<\/span>\n        <span class=\"n\">successView<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ MARK: - Login Form View<\/span>\n<span class=\"kd\">private<\/span> <span class=\"kd\">extension<\/span> <span class=\"kt\">AuthView<\/span> <span class=\"p\">{<\/span>\n  <span class=\"c1\">\/\/\/ The initial sign-in form displayed when the user is not authenticated.<\/span>\n  <span class=\"c1\">\/\/\/ Captures username and password input and triggers the DirectAuth sign-in flow.<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">loginForm<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">VStack<\/span><span class=\"p\">(<\/span><span class=\"nv\">spacing<\/span><span class=\"p\">:<\/span> <span class=\"mi\">16<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n      <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Okta DirectAuth (Password + Okta Verify Push)\"<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">headline<\/span><span class=\"p\">)<\/span>\n\n      <span class=\"c1\">\/\/ Email input field (bound to view model's username property)<\/span>\n      <span class=\"kt\">TextField<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Email\"<\/span><span class=\"p\">,<\/span> <span class=\"nv\">text<\/span><span class=\"p\">:<\/span> <span class=\"err\">$<\/span><span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"n\">username<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">keyboardType<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">emailAddress<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">textContentType<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">username<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">textInputAutocapitalization<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">never<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">autocorrectionDisabled<\/span><span class=\"p\">()<\/span>\n\n      <span class=\"c1\">\/\/ Secure password input field<\/span>\n      <span class=\"kt\">SecureField<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Password\"<\/span><span class=\"p\">,<\/span> <span class=\"nv\">text<\/span><span class=\"p\">:<\/span> <span class=\"err\">$<\/span><span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"n\">password<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">textContentType<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">password<\/span><span class=\"p\">)<\/span>\n\n      <span class=\"c1\">\/\/ Triggers authentication via DirectAuth and Push MFA<\/span>\n      <span class=\"kt\">Button<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Sign In\"<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"kt\">Task<\/span> <span class=\"p\">{<\/span> <span class=\"k\">await<\/span> <span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"nf\">signIn<\/span><span class=\"p\">()<\/span> <span class=\"p\">}<\/span>\n      <span class=\"p\">}<\/span>\n      <span class=\"o\">.<\/span><span class=\"nf\">buttonStyle<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">borderedProminent<\/span><span class=\"p\">)<\/span>\n      <span class=\"o\">.<\/span><span class=\"nf\">disabled<\/span><span class=\"p\">(<\/span><span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"n\">username<\/span><span class=\"o\">.<\/span><span class=\"n\">isEmpty<\/span> <span class=\"o\">||<\/span> <span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"n\">password<\/span><span class=\"o\">.<\/span><span class=\"n\">isEmpty<\/span><span class=\"p\">)<\/span>\n\n      <span class=\"c1\">\/\/ Display error message if sign-in fails<\/span>\n      <span class=\"k\">if<\/span> <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"nf\">failed<\/span><span class=\"p\">(<\/span><span class=\"k\">let<\/span> <span class=\"nv\">message<\/span><span class=\"p\">)<\/span> <span class=\"o\">=<\/span> <span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"n\">state<\/span> <span class=\"p\">{<\/span>\n        <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"n\">message<\/span><span class=\"p\">)<\/span>\n          <span class=\"o\">.<\/span><span class=\"nf\">foregroundColor<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">red<\/span><span class=\"p\">)<\/span>\n          <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">footnote<\/span><span class=\"p\">)<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ MARK: - Authorized State View<\/span>\n<span class=\"kd\">private<\/span> <span class=\"kd\">extension<\/span> <span class=\"kt\">AuthView<\/span> <span class=\"p\">{<\/span>\n  <span class=\"c1\">\/\/\/ Displayed once the user has successfully signed in and completed MFA.<\/span>\n  <span class=\"c1\">\/\/\/ Shows the user's ID token and provides actions for token refresh, user info,<\/span>\n  <span class=\"c1\">\/\/\/ token details, and sign-out.<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">successView<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">VStack<\/span><span class=\"p\">(<\/span><span class=\"nv\">spacing<\/span><span class=\"p\">:<\/span> <span class=\"mi\">16<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n      <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Signed in \ud83c\udf89\"<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">title2<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">bold<\/span><span class=\"p\">()<\/span>\n\n      <span class=\"c1\">\/\/ Scrollable ID token display (for demo purposes)<\/span>\n      <span class=\"kt\">ScrollView<\/span> <span class=\"p\">{<\/span>\n        <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"n\">token<\/span><span class=\"o\">.<\/span><span class=\"n\">idToken<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"n\">rawValue<\/span> <span class=\"p\">??<\/span> <span class=\"s\">\"(no id token)\"<\/span><span class=\"p\">)<\/span>\n          <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">footnote<\/span><span class=\"p\">)<\/span>\n          <span class=\"o\">.<\/span><span class=\"nf\">textSelection<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">enabled<\/span><span class=\"p\">)<\/span>\n          <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n          <span class=\"o\">.<\/span><span class=\"nf\">background<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">thinMaterial<\/span><span class=\"p\">)<\/span>\n          <span class=\"o\">.<\/span><span class=\"nf\">cornerRadius<\/span><span class=\"p\">(<\/span><span class=\"mi\">8<\/span><span class=\"p\">)<\/span>\n      <span class=\"p\">}<\/span>\n      <span class=\"o\">.<\/span><span class=\"nf\">frame<\/span><span class=\"p\">(<\/span><span class=\"nv\">maxHeight<\/span><span class=\"p\">:<\/span> <span class=\"mi\">220<\/span><span class=\"p\">)<\/span>\n\n      <span class=\"c1\">\/\/ Authenticated user actions<\/span>\n      <span class=\"n\">signoutButton<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ MARK: - Action Buttons<\/span>\n<span class=\"kd\">private<\/span> <span class=\"kd\">extension<\/span> <span class=\"kt\">AuthView<\/span> <span class=\"p\">{<\/span>\n  <span class=\"c1\">\/\/\/ Signs the user out, revoking tokens and returning to the login form.<\/span>\n  <span class=\"k\">var<\/span> <span class=\"nv\">signoutButton<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">Button<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Sign Out\"<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n      <span class=\"kt\">Task<\/span> <span class=\"p\">{<\/span> <span class=\"k\">await<\/span> <span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"nf\">signOut<\/span><span class=\"p\">()<\/span> <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"nf\">system<\/span><span class=\"p\">(<\/span><span class=\"nv\">size<\/span><span class=\"p\">:<\/span> <span class=\"mi\">14<\/span><span class=\"p\">))<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>With this added, you will receive an error stating that <code class=\"language-plaintext highlighter-rouge\">WaitingForPushView<\/code> can\u2019t be found in scope. To fix this, we need to add that view next. Add a new empty Swift file in the <code class=\"language-plaintext highlighter-rouge\">Views<\/code> folder and name it <code class=\"language-plaintext highlighter-rouge\">WaitingForPushView<\/code>. When complete, add the following implementation inside:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">import<\/span> <span class=\"kt\">SwiftUI<\/span>\n\n<span class=\"kd\">struct<\/span> <span class=\"kt\">WaitingForPushView<\/span><span class=\"p\">:<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">let<\/span> <span class=\"nv\">onCancel<\/span><span class=\"p\">:<\/span> <span class=\"p\">()<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"kt\">Void<\/span>\n\n  <span class=\"k\">var<\/span> <span class=\"nv\">body<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">VStack<\/span><span class=\"p\">(<\/span><span class=\"nv\">spacing<\/span><span class=\"p\">:<\/span> <span class=\"mi\">16<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n      <span class=\"kt\">ProgressView<\/span><span class=\"p\">()<\/span>\n      <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Approve the Okta Verify push on your device.\"<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">multilineTextAlignment<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">center<\/span><span class=\"p\">)<\/span>\n\n      <span class=\"kt\">Button<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Cancel\"<\/span><span class=\"p\">,<\/span> <span class=\"nv\">action<\/span><span class=\"p\">:<\/span> <span class=\"n\">onCancel<\/span><span class=\"p\">)<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Now you can run the application on a simulator, and it should present you with the option to log in first with a username and password. After selecting <strong>SignIn<\/strong>, it will redirect to the \u201cWaiting for push notification\u201d screen and remain active until you acknowledge the request from the Okta Verify App. If you\u2019re logged in, you\u2019ll see the access token and a sign-out button.<\/p>\n\n<h3 id=\"read-id-token-info\">Read ID token info<\/h3>\n\n<p>Once your app authenticates a user with Okta DirectAuth, the resulting credentials are securely stored in the device\u2019s keychain through <code class=\"language-plaintext highlighter-rouge\">AuthFoundation<\/code>.<\/p>\n\n<p>These credentials include access, ID, and (optionally) refresh tokens \u2013 all essential for securely calling APIs or verifying user identity.<\/p>\n\n<p>In this section, we\u2019ll create a skeleton <code class=\"language-plaintext highlighter-rouge\">TokenInfoView<\/code> that reads the current tokens from <code class=\"language-plaintext highlighter-rouge\">Credential.default<\/code> and displays them in a developer-friendly format.<\/p>\n\n<p>This view helps visualize the credential in the store and to inspect the scope. And it helps verify that the authentication flow works.<\/p>\n\n<p>Create a new Swift file in the <code class=\"language-plaintext highlighter-rouge\">Views<\/code> folder and name it <code class=\"language-plaintext highlighter-rouge\">TokenInfoView<\/code>. Add the following code:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">import<\/span> <span class=\"kt\">SwiftUI<\/span>\n<span class=\"kd\">import<\/span> <span class=\"kt\">AuthFoundation<\/span>\n\n<span class=\"c1\">\/\/\/ Displays detailed information about the tokens stored in the current<\/span>\n<span class=\"c1\">\/\/\/ `Credential.default` instance. This view is helpful for debugging and<\/span>\n<span class=\"c1\">\/\/\/ validating your DirectAuth flow -- confirming that tokens are correctly<\/span>\n<span class=\"c1\">\/\/\/ issued, stored, and refreshed.<\/span>\n<span class=\"c1\">\/\/\/<\/span>\n<span class=\"c1\">\/\/\/ \u26a0\ufe0f **Important:** Avoid showing full token strings in production apps.<\/span>\n<span class=\"c1\">\/\/\/ Tokens should be treated as sensitive secrets.<\/span>\n<span class=\"kd\">struct<\/span> <span class=\"kt\">TokenInfoView<\/span><span class=\"p\">:<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n\n  <span class=\"c1\">\/\/\/ Retrieves the current credential object managed by `AuthFoundation`.<\/span>\n  <span class=\"c1\">\/\/\/ If the user is signed in, this will contain their access, ID, and refresh tokens.<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">credential<\/span><span class=\"p\">:<\/span> <span class=\"kt\">Credential<\/span><span class=\"p\">?<\/span> <span class=\"p\">{<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span> <span class=\"p\">}<\/span>\n\n  <span class=\"c1\">\/\/\/ Used to dismiss the current view when the close button is tapped.<\/span>\n  <span class=\"kd\">@Environment<\/span><span class=\"p\">(\\<\/span><span class=\"o\">.<\/span><span class=\"n\">dismiss<\/span><span class=\"p\">)<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">dismiss<\/span>\n\n  <span class=\"k\">var<\/span> <span class=\"nv\">body<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">ScrollView<\/span> <span class=\"p\">{<\/span>\n        <span class=\"kt\">VStack<\/span><span class=\"p\">(<\/span><span class=\"nv\">alignment<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"n\">leading<\/span><span class=\"p\">,<\/span> <span class=\"nv\">spacing<\/span><span class=\"p\">:<\/span> <span class=\"mi\">20<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n\n          <span class=\"c1\">\/\/ MARK: - Close Button<\/span>\n          <span class=\"c1\">\/\/ Dismisses the token info view when tapped.<\/span>\n          <span class=\"kt\">Button<\/span> <span class=\"p\">{<\/span>\n            <span class=\"nf\">dismiss<\/span><span class=\"p\">()<\/span>\n          <span class=\"p\">}<\/span> <span class=\"nv\">label<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n            <span class=\"kt\">Image<\/span><span class=\"p\">(<\/span><span class=\"nv\">systemName<\/span><span class=\"p\">:<\/span> <span class=\"s\">\"xmark.circle.fill\"<\/span><span class=\"p\">)<\/span>\n              <span class=\"o\">.<\/span><span class=\"nf\">resizable<\/span><span class=\"p\">()<\/span>\n              <span class=\"o\">.<\/span><span class=\"nf\">foregroundStyle<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">black<\/span><span class=\"p\">)<\/span>\n              <span class=\"o\">.<\/span><span class=\"nf\">frame<\/span><span class=\"p\">(<\/span><span class=\"nv\">width<\/span><span class=\"p\">:<\/span> <span class=\"mi\">40<\/span><span class=\"p\">,<\/span> <span class=\"nv\">height<\/span><span class=\"p\">:<\/span> <span class=\"mi\">40<\/span><span class=\"p\">)<\/span>\n              <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">leading<\/span><span class=\"p\">,<\/span> <span class=\"mi\">10<\/span><span class=\"p\">)<\/span>\n          <span class=\"p\">}<\/span>\n\n          <span class=\"c1\">\/\/ MARK: - Token Display<\/span>\n          <span class=\"c1\">\/\/ Displays the token information as formatted monospaced text.<\/span>\n          <span class=\"c1\">\/\/ If no credential is available, a \"No token found\" message is shown.<\/span>\n          <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"n\">credential<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"nf\">toString<\/span><span class=\"p\">()<\/span> <span class=\"p\">??<\/span> <span class=\"s\">\"No token found\"<\/span><span class=\"p\">)<\/span>\n            <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"nf\">system<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">body<\/span><span class=\"p\">,<\/span> <span class=\"nv\">design<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"n\">monospaced<\/span><span class=\"p\">))<\/span>\n            <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n            <span class=\"o\">.<\/span><span class=\"nf\">frame<\/span><span class=\"p\">(<\/span><span class=\"nv\">maxWidth<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"n\">infinity<\/span><span class=\"p\">,<\/span> <span class=\"nv\">alignment<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"n\">leading<\/span><span class=\"p\">)<\/span>\n        <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">background<\/span><span class=\"p\">(<\/span><span class=\"kt\">Color<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">systemGroupedBackground<\/span><span class=\"p\">))<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">navigationTitle<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Token Info\"<\/span><span class=\"p\">)<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">navigationBarTitleDisplayMode<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">inline<\/span><span class=\"p\">)<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ MARK: - Credential Display Helper<\/span>\n\n<span class=\"kd\">extension<\/span> <span class=\"kt\">Credential<\/span> <span class=\"p\">{<\/span>\n  <span class=\"c1\">\/\/\/ Returns a formatted string representation of the stored token values.<\/span>\n  <span class=\"c1\">\/\/\/ Includes access, ID, and refresh tokens as well as their associated scopes.<\/span>\n  <span class=\"c1\">\/\/\/<\/span>\n  <span class=\"c1\">\/\/\/ - Returns: A multi-line string suitable for debugging and display in `TokenInfoView`.<\/span>\n  <span class=\"kd\">func<\/span> <span class=\"nf\">toString<\/span><span class=\"p\">()<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"kt\">String<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">var<\/span> <span class=\"nv\">result<\/span> <span class=\"o\">=<\/span> <span class=\"s\">\"\"<\/span>\n\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Token type: <\/span><span class=\"se\">\\(<\/span><span class=\"n\">token<\/span><span class=\"o\">.<\/span><span class=\"n\">tokenType<\/span><span class=\"se\">)<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"<\/span><span class=\"se\">\\n\\n<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Access Token: <\/span><span class=\"se\">\\(<\/span><span class=\"n\">token<\/span><span class=\"o\">.<\/span><span class=\"n\">accessToken<\/span><span class=\"se\">)<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"<\/span><span class=\"se\">\\n\\n<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Scopes: <\/span><span class=\"se\">\\(<\/span><span class=\"n\">token<\/span><span class=\"o\">.<\/span><span class=\"n\">scope<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"nf\">joined<\/span><span class=\"p\">(<\/span><span class=\"nv\">separator<\/span><span class=\"p\">:<\/span> <span class=\"s\">\",\"<\/span><span class=\"p\">)<\/span> <span class=\"p\">??<\/span> <span class=\"s\">\"No scopes found\"<\/span><span class=\"se\">)<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"<\/span><span class=\"se\">\\n\\n<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"k\">if<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">idToken<\/span> <span class=\"o\">=<\/span> <span class=\"n\">token<\/span><span class=\"o\">.<\/span><span class=\"n\">idToken<\/span> <span class=\"p\">{<\/span>\n      <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"ID Token: <\/span><span class=\"se\">\\(<\/span><span class=\"n\">idToken<\/span><span class=\"o\">.<\/span><span class=\"n\">rawValue<\/span><span class=\"se\">)<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n      <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"<\/span><span class=\"se\">\\n\\n<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n    <span class=\"p\">}<\/span>\n\n    <span class=\"k\">if<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">refreshToken<\/span> <span class=\"o\">=<\/span> <span class=\"n\">token<\/span><span class=\"o\">.<\/span><span class=\"n\">refreshToken<\/span> <span class=\"p\">{<\/span>\n      <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Refresh Token: <\/span><span class=\"se\">\\(<\/span><span class=\"n\">refreshToken<\/span><span class=\"se\">)<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n      <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"<\/span><span class=\"se\">\\n\\n<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n    <span class=\"p\">}<\/span>\n\n    <span class=\"k\">return<\/span> <span class=\"n\">result<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>To view this on screen, we need to instruct SwiftUI to present it. We added the <code class=\"language-plaintext highlighter-rouge\">State<\/code> variable in the <code class=\"language-plaintext highlighter-rouge\">AuthView<\/code> for this purpose - it\u2019s named <code class=\"language-plaintext highlighter-rouge\">showTokenInfo<\/code>. Next, we need to add a button to present the <code class=\"language-plaintext highlighter-rouge\">TokenInfoView<\/code>. Go to the <code class=\"language-plaintext highlighter-rouge\">AuthView.swift<\/code> and scroll down to the last private extension where it says \u201cAction Buttons\u201d and add the following button:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/\/ Opens the full-screen view showing token info.<\/span>\n<span class=\"k\">var<\/span> <span class=\"nv\">tokenInfoButton<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kt\">Button<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Token Info\"<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"n\">showTokenInfo<\/span> <span class=\"o\">=<\/span> <span class=\"kc\">true<\/span>\n  <span class=\"p\">}<\/span>\n  <span class=\"o\">.<\/span><span class=\"nf\">disabled<\/span><span class=\"p\">(<\/span><span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"n\">isLoading<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Now that this is in place, we need to tell SwiftUI that we want to present <code class=\"language-plaintext highlighter-rouge\">TokenInfoView<\/code> whenever the <code class=\"language-plaintext highlighter-rouge\">showTokenInfo<\/code> boolean is true. In the <code class=\"language-plaintext highlighter-rouge\">AuthView<\/code>, find the body and add this code at the end below the <code class=\"language-plaintext highlighter-rouge\">.padding()<\/code>:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ Show Token Info full screen<\/span>\n<span class=\"o\">.<\/span><span class=\"nf\">fullScreenCover<\/span><span class=\"p\">(<\/span><span class=\"nv\">isPresented<\/span><span class=\"p\">:<\/span> <span class=\"err\">$<\/span><span class=\"n\">showTokenInfo<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kt\">TokenInfoView<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>If you build and run the app, you\u2019ll no longer see the <strong>Token Info<\/strong> button when logged in. To keep the button visible, we also need to reference the <code class=\"language-plaintext highlighter-rouge\">tokenInfoButton<\/code> in the <code class=\"language-plaintext highlighter-rouge\">successView<\/code>. In the <code class=\"language-plaintext highlighter-rouge\">AuthView<\/code> file, scroll down to \u201cAuthorized State View\u201d (<code class=\"language-plaintext highlighter-rouge\">successView<\/code>) and reference the button just above the <code class=\"language-plaintext highlighter-rouge\">signoutButton<\/code> like this:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">private<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">successView<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kt\">VStack<\/span><span class=\"p\">(<\/span><span class=\"nv\">spacing<\/span><span class=\"p\">:<\/span> <span class=\"mi\">16<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Signed in \ud83c\udf89\"<\/span><span class=\"p\">)<\/span>\n      <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">title2<\/span><span class=\"p\">)<\/span>\n      <span class=\"o\">.<\/span><span class=\"nf\">bold<\/span><span class=\"p\">()<\/span>\n\n    <span class=\"c1\">\/\/ Scrollable ID token display (for demo purposes)<\/span>\n    <span class=\"kt\">ScrollView<\/span> <span class=\"p\">{<\/span>\n      <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"n\">token<\/span><span class=\"o\">.<\/span><span class=\"n\">idToken<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"n\">rawValue<\/span> <span class=\"p\">??<\/span> <span class=\"s\">\"(no id token)\"<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">footnote<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">textSelection<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">enabled<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">background<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">thinMaterial<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">cornerRadius<\/span><span class=\"p\">(<\/span><span class=\"mi\">8<\/span><span class=\"p\">)<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">frame<\/span><span class=\"p\">(<\/span><span class=\"nv\">maxHeight<\/span><span class=\"p\">:<\/span> <span class=\"mi\">220<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"c1\">\/\/ Authenticated user actions<\/span>\n    <span class=\"n\">tokenInfoButton<\/span> <span class=\"c1\">\/\/ this is added<\/span>\n    <span class=\"n\">signoutButton<\/span>\n  <span class=\"p\">}<\/span>\n  <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Try building and running the app. You should now see the <strong>Token Info<\/strong> button after logging in. Tapping the button should open the Token Info View.<\/p>\n\n<h2 id=\"view-the-authenticated-users-profile-info\">View the authenticated user\u2019s profile info<\/h2>\n\n<p>Once your app authenticates a user with Okta DirectAuth, it can use the stored credentials to request profile information from the <code class=\"language-plaintext highlighter-rouge\">UserInfo<\/code> endpoint securely.<\/p>\n\n<p>This endpoint returns standard OpenID Connect (OIDC) claims, including the user\u2019s name, email address, and unique identifier (<code class=\"language-plaintext highlighter-rouge\">sub<\/code>).<\/p>\n\n<p>In this section, you\u2019ll add a <strong>User Info<\/strong> button to your authenticated view and implement a corresponding <code class=\"language-plaintext highlighter-rouge\">UserInfoView<\/code> that displays these profile details.<\/p>\n\n<p>This is a quick and powerful way to confirm the validity of the access token and that your app can retrieve user data after sign-in.<\/p>\n\n<p>Create a new empty Swift file in the <code class=\"language-plaintext highlighter-rouge\">Views<\/code> folder and name it <code class=\"language-plaintext highlighter-rouge\">UserInfoView<\/code>. Then add the following code:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">import<\/span> <span class=\"kt\">SwiftUI<\/span>\n<span class=\"kd\">import<\/span> <span class=\"kt\">AuthFoundation<\/span>\n\n<span class=\"c1\">\/\/\/ A view that displays the authenticated user's profile information<\/span>\n<span class=\"c1\">\/\/\/ retrieved from Okta's **UserInfo** endpoint.<\/span>\n<span class=\"c1\">\/\/\/<\/span>\n<span class=\"c1\">\/\/\/ The `UserInfo` object is provided by **AuthFoundation** and contains<\/span>\n<span class=\"c1\">\/\/\/ standard OpenID Connect (OIDC) claims such as `name`, `preferred_username`,<\/span>\n<span class=\"c1\">\/\/\/ and `sub` (subject identifier). This view is shown after the user has<\/span>\n<span class=\"c1\">\/\/\/ successfully authenticated, allowing you to confirm that your access token<\/span>\n<span class=\"c1\">\/\/\/ can retrieve user data.<\/span>\n<span class=\"kd\">struct<\/span> <span class=\"kt\">UserInfoView<\/span><span class=\"p\">:<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n\n  <span class=\"c1\">\/\/\/ The user information returned by the Okta UserInfo endpoint.<\/span>\n  <span class=\"k\">let<\/span> <span class=\"nv\">userInfo<\/span><span class=\"p\">:<\/span> <span class=\"kt\">UserInfo<\/span>\n\n  <span class=\"c1\">\/\/\/ Used to dismiss the view when the close button is tapped.<\/span>\n  <span class=\"kd\">@Environment<\/span><span class=\"p\">(\\<\/span><span class=\"o\">.<\/span><span class=\"n\">dismiss<\/span><span class=\"p\">)<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">dismiss<\/span>\n\n  <span class=\"k\">var<\/span> <span class=\"nv\">body<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">ScrollView<\/span> <span class=\"p\">{<\/span>\n      <span class=\"kt\">VStack<\/span><span class=\"p\">(<\/span><span class=\"nv\">alignment<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"n\">leading<\/span><span class=\"p\">,<\/span> <span class=\"nv\">spacing<\/span><span class=\"p\">:<\/span> <span class=\"mi\">20<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n\n          <span class=\"c1\">\/\/ MARK: - Close Button<\/span>\n          <span class=\"c1\">\/\/ Dismisses the full-screen user info view.<\/span>\n          <span class=\"kt\">Button<\/span> <span class=\"p\">{<\/span>\n            <span class=\"nf\">dismiss<\/span><span class=\"p\">()<\/span>\n          <span class=\"p\">}<\/span> <span class=\"nv\">label<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n            <span class=\"kt\">Image<\/span><span class=\"p\">(<\/span><span class=\"nv\">systemName<\/span><span class=\"p\">:<\/span> <span class=\"s\">\"xmark.circle.fill\"<\/span><span class=\"p\">)<\/span>\n              <span class=\"o\">.<\/span><span class=\"nf\">resizable<\/span><span class=\"p\">()<\/span>\n              <span class=\"o\">.<\/span><span class=\"nf\">foregroundStyle<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">black<\/span><span class=\"p\">)<\/span>\n              <span class=\"o\">.<\/span><span class=\"nf\">frame<\/span><span class=\"p\">(<\/span><span class=\"nv\">width<\/span><span class=\"p\">:<\/span> <span class=\"mi\">40<\/span><span class=\"p\">,<\/span> <span class=\"nv\">height<\/span><span class=\"p\">:<\/span> <span class=\"mi\">40<\/span><span class=\"p\">)<\/span>\n              <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">leading<\/span><span class=\"p\">,<\/span> <span class=\"mi\">10<\/span><span class=\"p\">)<\/span>\n          <span class=\"p\">}<\/span>\n\n          <span class=\"c1\">\/\/ MARK: - User Information Text<\/span>\n          <span class=\"c1\">\/\/ Displays formatted user claims (name, username, subject, etc.)<\/span>\n          <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"n\">formattedData<\/span><span class=\"p\">)<\/span>\n            <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"nf\">system<\/span><span class=\"p\">(<\/span><span class=\"nv\">size<\/span><span class=\"p\">:<\/span> <span class=\"mi\">14<\/span><span class=\"p\">))<\/span>\n            <span class=\"o\">.<\/span><span class=\"nf\">frame<\/span><span class=\"p\">(<\/span><span class=\"nv\">maxWidth<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"n\">infinity<\/span><span class=\"p\">,<\/span> <span class=\"nv\">alignment<\/span><span class=\"p\">:<\/span> <span class=\"o\">.<\/span><span class=\"n\">leading<\/span><span class=\"p\">)<\/span>\n            <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n        <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">background<\/span><span class=\"p\">(<\/span><span class=\"kt\">Color<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">systemBackground<\/span><span class=\"p\">))<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">navigationTitle<\/span><span class=\"p\">(<\/span><span class=\"s\">\"User Info\"<\/span><span class=\"p\">)<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">navigationBarTitleDisplayMode<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">inline<\/span><span class=\"p\">)<\/span>\n  <span class=\"p\">}<\/span>\n\n  <span class=\"c1\">\/\/ MARK: - Data Formatting<\/span>\n\n  <span class=\"c1\">\/\/\/ Builds a simple multi-line string of readable user information.<\/span>\n  <span class=\"c1\">\/\/\/ Extracts common OIDC claims and formats them for display.<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">formattedData<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">var<\/span> <span class=\"nv\">result<\/span> <span class=\"o\">=<\/span> <span class=\"s\">\"\"<\/span>\n\n    <span class=\"c1\">\/\/ User's full name<\/span>\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Name: \"<\/span> <span class=\"o\">+<\/span> <span class=\"p\">(<\/span><span class=\"n\">userInfo<\/span><span class=\"o\">.<\/span><span class=\"n\">name<\/span> <span class=\"p\">??<\/span> <span class=\"s\">\"No name set\"<\/span><span class=\"p\">))<\/span>\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"<\/span><span class=\"se\">\\n\\n<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"c1\">\/\/ Preferred username (email or login identifier)<\/span>\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Username: \"<\/span> <span class=\"o\">+<\/span> <span class=\"p\">(<\/span><span class=\"n\">userInfo<\/span><span class=\"o\">.<\/span><span class=\"n\">preferredUsername<\/span> <span class=\"p\">??<\/span> <span class=\"s\">\"No username set\"<\/span><span class=\"p\">))<\/span>\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"<\/span><span class=\"se\">\\n\\n<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"c1\">\/\/ Subject identifier (unique Okta user ID)<\/span>\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"User ID: \"<\/span> <span class=\"o\">+<\/span> <span class=\"p\">(<\/span><span class=\"n\">userInfo<\/span><span class=\"o\">.<\/span><span class=\"n\">subject<\/span> <span class=\"p\">??<\/span> <span class=\"s\">\"No ID found\"<\/span><span class=\"p\">))<\/span>\n    <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"<\/span><span class=\"se\">\\n\\n<\/span><span class=\"s\">\"<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"c1\">\/\/ Last updated timestamp (if available)<\/span>\n    <span class=\"k\">if<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">updatedAt<\/span> <span class=\"o\">=<\/span> <span class=\"n\">userInfo<\/span><span class=\"o\">.<\/span><span class=\"n\">updatedAt<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">let<\/span> <span class=\"nv\">dateFormatter<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">DateFormatter<\/span><span class=\"p\">()<\/span>\n      <span class=\"n\">dateFormatter<\/span><span class=\"o\">.<\/span><span class=\"n\">dateStyle<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"n\">medium<\/span>\n      <span class=\"n\">dateFormatter<\/span><span class=\"o\">.<\/span><span class=\"n\">timeStyle<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"n\">short<\/span>\n      <span class=\"k\">let<\/span> <span class=\"nv\">formattedDate<\/span> <span class=\"o\">=<\/span> <span class=\"n\">dateFormatter<\/span><span class=\"o\">.<\/span><span class=\"nf\">string<\/span><span class=\"p\">(<\/span><span class=\"nv\">for<\/span><span class=\"p\">:<\/span> <span class=\"n\">updatedAt<\/span><span class=\"p\">)<\/span>\n      <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"nf\">append<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Updated at: \"<\/span> <span class=\"o\">+<\/span> <span class=\"p\">(<\/span><span class=\"n\">formattedDate<\/span> <span class=\"p\">??<\/span> <span class=\"s\">\"\"<\/span><span class=\"p\">))<\/span>\n    <span class=\"p\">}<\/span>\n\n    <span class=\"k\">return<\/span> <span class=\"n\">result<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Once again, to display this in our app, we need to add a new button to show the new view. To do that, open the <code class=\"language-plaintext highlighter-rouge\">AuthView.swift<\/code>, scroll down to the last private extension where it says \u201cAction Buttons\u201d, and add the following button just below the <code class=\"language-plaintext highlighter-rouge\">tokenInfoButton<\/code>:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/\/ Loads user info and presents it full screen.<\/span>\n<span class=\"kd\">@MainActor<\/span>\n<span class=\"k\">var<\/span> <span class=\"nv\">userInfoButton<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kt\">Button<\/span><span class=\"p\">(<\/span><span class=\"s\">\"User Info\"<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">Task<\/span> <span class=\"p\">{<\/span>\n      <span class=\"k\">if<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">user<\/span> <span class=\"o\">=<\/span> <span class=\"k\">await<\/span> <span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"nf\">fetchUserInfo<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n        <span class=\"n\">userInfo<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">UserInfoModel<\/span><span class=\"p\">(<\/span><span class=\"nv\">user<\/span><span class=\"p\">:<\/span> <span class=\"n\">user<\/span><span class=\"p\">)<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">}<\/span>\n  <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"nf\">system<\/span><span class=\"p\">(<\/span><span class=\"nv\">size<\/span><span class=\"p\">:<\/span> <span class=\"mi\">14<\/span><span class=\"p\">))<\/span>\n  <span class=\"o\">.<\/span><span class=\"nf\">disabled<\/span><span class=\"p\">(<\/span><span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"n\">isLoading<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Next, we need to add the button to the <code class=\"language-plaintext highlighter-rouge\">successView<\/code> like we did with the <code class=\"language-plaintext highlighter-rouge\">tokenInfoButton<\/code>. Then, we will use the <code class=\"language-plaintext highlighter-rouge\">userInfo<\/code> property in the <code class=\"language-plaintext highlighter-rouge\">AuthView<\/code>, which we added at the start. Navigate to the <code class=\"language-plaintext highlighter-rouge\">AuthView.swift<\/code> file and find the <code class=\"language-plaintext highlighter-rouge\">successView<\/code> in the \u201cAuthorized State View\u201d mark and reference the <code class=\"language-plaintext highlighter-rouge\">userInfoButton<\/code> after the <code class=\"language-plaintext highlighter-rouge\">tokenInfoButton<\/code> like this:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">private<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">successView<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kt\">VStack<\/span><span class=\"p\">(<\/span><span class=\"nv\">spacing<\/span><span class=\"p\">:<\/span> <span class=\"mi\">16<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Signed in \ud83c\udf89\"<\/span><span class=\"p\">)<\/span>\n      <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">title2<\/span><span class=\"p\">)<\/span>\n      <span class=\"o\">.<\/span><span class=\"nf\">bold<\/span><span class=\"p\">()<\/span>\n\n    <span class=\"c1\">\/\/ Scrollable ID token display (for demo purposes)<\/span>\n    <span class=\"kt\">ScrollView<\/span> <span class=\"p\">{<\/span>\n      <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"n\">token<\/span><span class=\"o\">.<\/span><span class=\"n\">idToken<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"n\">rawValue<\/span> <span class=\"p\">??<\/span> <span class=\"s\">\"(no id token)\"<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">footnote<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">textSelection<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">enabled<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">background<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">thinMaterial<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">cornerRadius<\/span><span class=\"p\">(<\/span><span class=\"mi\">8<\/span><span class=\"p\">)<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">frame<\/span><span class=\"p\">(<\/span><span class=\"nv\">maxHeight<\/span><span class=\"p\">:<\/span> <span class=\"mi\">220<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"c1\">\/\/ Authenticated user actions<\/span>\n    <span class=\"n\">tokenInfoButton<\/span>\n    <span class=\"n\">userInfoButton<\/span> <span class=\"c1\">\/\/ this is added<\/span>\n    <span class=\"n\">signoutButton<\/span>\n  <span class=\"p\">}<\/span>\n  <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>We need to tell SwiftUI that we want to open a new <code class=\"language-plaintext highlighter-rouge\">UserInfoView<\/code> whenever the value on the <code class=\"language-plaintext highlighter-rouge\">userInfo<\/code> property changes. To do so, open the <code class=\"language-plaintext highlighter-rouge\">AuthView<\/code> and find the body variable, add the following code after the last closing bracket:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/ Show User Info full screen<\/span>\n<span class=\"o\">.<\/span><span class=\"nf\">fullScreenCover<\/span><span class=\"p\">(<\/span><span class=\"nv\">item<\/span><span class=\"p\">:<\/span> <span class=\"err\">$<\/span><span class=\"n\">userInfo<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span> <span class=\"n\">info<\/span> <span class=\"k\">in<\/span>\n  <span class=\"kt\">UserInfoView<\/span><span class=\"p\">(<\/span><span class=\"nv\">userInfo<\/span><span class=\"p\">:<\/span> <span class=\"n\">info<\/span><span class=\"o\">.<\/span><span class=\"n\">user<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The body of your <code class=\"language-plaintext highlighter-rouge\">AuthView<\/code> should look like this now:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">var<\/span> <span class=\"nv\">body<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kt\">VStack<\/span> <span class=\"p\">{<\/span>\n    <span class=\"c1\">\/\/ Render the UI based on the current authentication state.<\/span>\n    <span class=\"c1\">\/\/ Each case corresponds to a different phase of the DirectAuth flow.<\/span>\n    <span class=\"k\">switch<\/span> <span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"n\">state<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"n\">idle<\/span><span class=\"p\">,<\/span> <span class=\"o\">.<\/span><span class=\"nv\">failed<\/span><span class=\"p\">:<\/span>\n      <span class=\"n\">loginForm<\/span>\n    <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"nv\">authenticating<\/span><span class=\"p\">:<\/span>\n      <span class=\"kt\">ProgressView<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Signing in...\"<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"nv\">waitingForPush<\/span><span class=\"p\">:<\/span>\n      <span class=\"c1\">\/\/ Waiting for Okta Verify push approval<\/span>\n      <span class=\"kt\">WaitingForPushView<\/span> <span class=\"p\">{<\/span>\n        <span class=\"kt\">Task<\/span> <span class=\"p\">{<\/span> <span class=\"k\">await<\/span> <span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"nf\">signOut<\/span><span class=\"p\">()<\/span> <span class=\"p\">}<\/span>\n      <span class=\"p\">}<\/span>\n    <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"nv\">authorized<\/span><span class=\"p\">:<\/span>\n      <span class=\"n\">successView<\/span>\n    <span class=\"p\">}<\/span>\n\n    <span class=\"k\">if<\/span> <span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"n\">isLoading<\/span> <span class=\"p\">{<\/span>\n      <span class=\"kt\">ProgressView<\/span><span class=\"p\">()<\/span>\n    <span class=\"p\">}<\/span>\n  <span class=\"p\">}<\/span>\n  <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n  <span class=\"c1\">\/\/ Show Token Info full screen<\/span>\n  <span class=\"o\">.<\/span><span class=\"nf\">fullScreenCover<\/span><span class=\"p\">(<\/span><span class=\"nv\">isPresented<\/span><span class=\"p\">:<\/span> <span class=\"err\">$<\/span><span class=\"n\">showTokenInfo<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">TokenInfoView<\/span><span class=\"p\">()<\/span>\n  <span class=\"p\">}<\/span>\n  <span class=\"c1\">\/\/ Show User Info full screen<\/span>\n  <span class=\"o\">.<\/span><span class=\"nf\">fullScreenCover<\/span><span class=\"p\">(<\/span><span class=\"nv\">item<\/span><span class=\"p\">:<\/span> <span class=\"err\">$<\/span><span class=\"n\">userInfo<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span> <span class=\"n\">info<\/span> <span class=\"k\">in<\/span>\n    <span class=\"kt\">UserInfoView<\/span><span class=\"p\">(<\/span><span class=\"nv\">userInfo<\/span><span class=\"p\">:<\/span> <span class=\"n\">info<\/span><span class=\"o\">.<\/span><span class=\"n\">user<\/span><span class=\"p\">)<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h3 id=\"keeping-tokens-refreshed-and-maintaining-user-sessions\">Keeping tokens refreshed and maintaining user sessions<\/h3>\n\n<p>Access tokens have a limited lifetime to ensure your app\u2019s security. When a token expires, the user shouldn\u2019t have to sign-in again \u2013 instead, your app can request a new access token using the refresh token stored in the credential.<\/p>\n\n<p>In this section, you\u2019ll add support for token refresh, allowing users to stay authenticated without repeating the entire sign-in and MFA flow.<\/p>\n\n<p>You\u2019ll add an action in the UI that calls the <code class=\"language-plaintext highlighter-rouge\">refreshTokenIfNeeded()<\/code> method from your <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code>, which silently exchanges the refresh token for a new set of valid tokens. We\u2019re making this call manually, but you can watch for upcoming expiry and refresh the token before it happens preemptively. While we don\u2019t show it here, you should use <strong>Refresh Token Rotation<\/strong> to ensure refresh tokens are also short-lived as a security measure.<\/p>\n\n<p>First, we need to add the <code class=\"language-plaintext highlighter-rouge\">refreshTokenButton<\/code>, which we\u2019ll add to the <code class=\"language-plaintext highlighter-rouge\">AuthView<\/code>. Open the <code class=\"language-plaintext highlighter-rouge\">AuthView<\/code>, scroll down to the last private extension in the \u201cAction Buttons\u201d mark, and add the following button at the end of the extension:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\">\/\/\/ Refresh Token if needed<\/span>\n<span class=\"k\">var<\/span> <span class=\"nv\">refreshTokenButton<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kt\">Button<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Refresh Token\"<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">Task<\/span> <span class=\"p\">{<\/span> <span class=\"k\">await<\/span> <span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"nf\">refreshToken<\/span><span class=\"p\">()<\/span> <span class=\"p\">}<\/span>\n  <span class=\"p\">}<\/span>\n  <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"nf\">system<\/span><span class=\"p\">(<\/span><span class=\"nv\">size<\/span><span class=\"p\">:<\/span> <span class=\"mi\">14<\/span><span class=\"p\">))<\/span>\n  <span class=\"o\">.<\/span><span class=\"nf\">disabled<\/span><span class=\"p\">(<\/span><span class=\"n\">viewModel<\/span><span class=\"o\">.<\/span><span class=\"n\">isLoading<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Next, we need to reference the button somewhere in our view. We will do that inside the <code class=\"language-plaintext highlighter-rouge\">successView<\/code>, like we did with the other buttons. Find the <code class=\"language-plaintext highlighter-rouge\">successView<\/code> and add the button. Your <code class=\"language-plaintext highlighter-rouge\">successView<\/code> should look like this:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">private<\/span> <span class=\"k\">var<\/span> <span class=\"nv\">successView<\/span><span class=\"p\">:<\/span> <span class=\"kd\">some<\/span> <span class=\"kt\">View<\/span> <span class=\"p\">{<\/span>\n  <span class=\"kt\">VStack<\/span><span class=\"p\">(<\/span><span class=\"nv\">spacing<\/span><span class=\"p\">:<\/span> <span class=\"mi\">16<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n    <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"s\">\"Signed in \ud83c\udf89\"<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">title2<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">bold<\/span><span class=\"p\">()<\/span>\n\n    <span class=\"c1\">\/\/ Scrollable ID token display (for demo purposes)<\/span>\n    <span class=\"kt\">ScrollView<\/span> <span class=\"p\">{<\/span>\n      <span class=\"kt\">Text<\/span><span class=\"p\">(<\/span><span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"n\">token<\/span><span class=\"o\">.<\/span><span class=\"n\">idToken<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"n\">rawValue<\/span> <span class=\"p\">??<\/span> <span class=\"s\">\"(no id token)\"<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">font<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">footnote<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">textSelection<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">enabled<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">background<\/span><span class=\"p\">(<\/span><span class=\"o\">.<\/span><span class=\"n\">thinMaterial<\/span><span class=\"p\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"nf\">cornerRadius<\/span><span class=\"p\">(<\/span><span class=\"mi\">8<\/span><span class=\"p\">)<\/span>\n    <span class=\"p\">}<\/span>\n    <span class=\"o\">.<\/span><span class=\"nf\">frame<\/span><span class=\"p\">(<\/span><span class=\"nv\">maxHeight<\/span><span class=\"p\">:<\/span> <span class=\"mi\">220<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"c1\">\/\/ Authenticated user actions<\/span>\n    <span class=\"n\">tokenInfoButton<\/span>\n    <span class=\"n\">userInfoButton<\/span>\n    <span class=\"n\">refreshTokenButton<\/span> <span class=\"c1\">\/\/ this is added<\/span>\n    <span class=\"n\">signoutButton<\/span>\n  <span class=\"p\">}<\/span>\n  <span class=\"o\">.<\/span><span class=\"nf\">padding<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Now, if you run the app and tap the <code class=\"language-plaintext highlighter-rouge\">refreshTokenButton<\/code>, you should see your token change in the token preview label.<\/p>\n\n<p>One thing that we didn\u2019t implement and left with a default implementation to return <code class=\"language-plaintext highlighter-rouge\">nil<\/code> is the <code class=\"language-plaintext highlighter-rouge\">accessToken<\/code> property on the <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code>. Navigate to the <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code>, find the <code class=\"language-plaintext highlighter-rouge\">accessToken<\/code> property, and replace the code so it looks like this:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">var<\/span> <span class=\"nv\">accessToken<\/span><span class=\"p\">:<\/span> <span class=\"kt\">String<\/span><span class=\"p\">?<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">switch<\/span> <span class=\"n\">state<\/span> <span class=\"p\">{<\/span>\n  <span class=\"k\">case<\/span> <span class=\"o\">.<\/span><span class=\"nf\">authorized<\/span><span class=\"p\">(<\/span><span class=\"k\">let<\/span> <span class=\"nv\">token<\/span><span class=\"p\">):<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">token<\/span><span class=\"o\">.<\/span><span class=\"n\">accessToken<\/span>\n  <span class=\"k\">default<\/span><span class=\"p\">:<\/span>\n    <span class=\"k\">return<\/span> <span class=\"kc\">nil<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Currently, if you restart the app, you\u2019ll get a prompt to log in each time. This is not a good user experience, and the user should remain logged in. We can add this feature by adding code in the <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code> initializer. Open your <code class=\"language-plaintext highlighter-rouge\">AuthService<\/code> class and replace the <code class=\"language-plaintext highlighter-rouge\">init<\/code> function with the following:<\/p>\n\n<div class=\"language-swift highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nf\">init<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n  <span class=\"c1\">\/\/ Prefer PropertyListConfiguration if Okta.plist exists; otherwise fall back<\/span>\n  <span class=\"k\">if<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">configuration<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span><span class=\"p\">?<\/span> <span class=\"kt\">OAuth2Client<\/span><span class=\"o\">.<\/span><span class=\"kt\">PropertyListConfiguration<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">self<\/span><span class=\"o\">.<\/span><span class=\"n\">flow<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span><span class=\"p\">?<\/span> <span class=\"kt\">DirectAuthenticationFlow<\/span><span class=\"p\">(<\/span><span class=\"nv\">client<\/span><span class=\"p\">:<\/span> <span class=\"kt\">OAuth2Client<\/span><span class=\"p\">(<\/span><span class=\"n\">configuration<\/span><span class=\"p\">))<\/span>\n  <span class=\"p\">}<\/span> <span class=\"k\">else<\/span> <span class=\"p\">{<\/span>\n    <span class=\"k\">self<\/span><span class=\"o\">.<\/span><span class=\"n\">flow<\/span> <span class=\"o\">=<\/span> <span class=\"k\">try<\/span><span class=\"p\">?<\/span> <span class=\"kt\">DirectAuthenticationFlow<\/span><span class=\"p\">()<\/span>\n  <span class=\"p\">}<\/span>\n\n  <span class=\"c1\">\/\/ Added<\/span>\n  <span class=\"k\">if<\/span> <span class=\"k\">let<\/span> <span class=\"nv\">token<\/span> <span class=\"o\">=<\/span> <span class=\"kt\">Credential<\/span><span class=\"o\">.<\/span><span class=\"k\">default<\/span><span class=\"p\">?<\/span><span class=\"o\">.<\/span><span class=\"n\">token<\/span> <span class=\"p\">{<\/span>\n    <span class=\"n\">state<\/span> <span class=\"o\">=<\/span> <span class=\"o\">.<\/span><span class=\"nf\">authorized<\/span><span class=\"p\">(<\/span><span class=\"n\">token<\/span><span class=\"p\">)<\/span>\n  <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n<h2 id=\"build-your-own-secure-native-sign-in-ios-app\">Build your own secure native sign-in iOS app<\/h2>\n\n<p>You\u2019ve now built a fully native authentication flow on iOS using Okta DirectAuth with push notification MFA \u2013 no browser redirects required. You can check your work against <a href=\"https:\/\/github.com\/oktadev\/okta-ios-swift-directauth-example\">the GitHub repo<\/a> for this project.<\/p>\n\n<p>Your app securely signs users in, handles multi-factor verification through Okta Verify, retrieves user profile details, displays token information, and refreshes tokens to maintain an active session.\nBy combining <code class=\"language-plaintext highlighter-rouge\">AuthFoundation<\/code> and <code class=\"language-plaintext highlighter-rouge\">OktaDirectAuth<\/code>, you\u2019ve implemented a modern, phishing-resistant authentication system that balances strong security with a seamless user experience \u2013 all directly within your SwiftUI app.<\/p>\n\n<p>If you found this post interesting, you may want to check out these resources:<\/p>\n<ul>\n  <li><a href=\"\/blog\/2025\/08\/20\/ios-mfa\">How to Build a Secure iOS App with MFA<\/a><\/li>\n  <li><a href=\"\/blog\/2022\/08\/30\/introducing-the-new-okta-mobile-sdks\">Introducing the New Okta Mobile SDKs<\/a><\/li>\n  <li><a href=\"\/blog\/2022\/01\/13\/mobile-sso\">A History of the Mobile SSO (Single Sign-On) Experience in iOS<\/a><\/li>\n<\/ul>\n\n<p>Follow OktaDev on <a href=\"https:\/\/twitter.com\/oktadev\">Twitter<\/a> and subscribe to our <a href=\"https:\/\/www.youtube.com\/c\/OktaDev\/\">YouTube channel<\/a> to learn about secure authentication and other exciting content. We also want to hear from you about topics you want to see and questions you may have. Leave us a comment below!<\/p>\n","pubDate":"Tue, 18 Nov 2025 00:00:00 -0500","link":"https:\/\/developer.okta.com\/blog\/2025\/11\/18\/okta-ios-directauth","guid":"https:\/\/developer.okta.com\/blog\/2025\/11\/18\/okta-ios-directauth"}]}}