<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Ping Identity Developer Portal Blog</title><description>News, guidance, and deep dives from the teams building Ping Identity developer tools.</description><link>https://developer.pingidentity.com/</link><item><title>Ping CLI 1.0: A Command Line Utility for the Ping Platform</title><link>https://developer.pingidentity.com/blog/ping-cli-v1-0-a-command-line-utility-for-the-ping-platform/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/ping-cli-v1-0-a-command-line-utility-for-the-ping-platform/</guid><description>Ping CLI has reached general availability — learn what it does, how it supports config-as-code and CI/CD for PingOne services, and where it is headed.</description><pubDate>Thu, 09 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We’re excited to share that &lt;strong&gt;Ping CLI has reached version 1.0&lt;/strong&gt;, a long-term supported, generally available release. Ping CLI is a command-line tool that connects to multiple Ping Identity services simultaneously, allowing you to manage them through one consistent, developer-first terminal interface.&lt;/p&gt;
&lt;p&gt;Ping Identity has a broad platform, and Ping CLI enables standard management patterns through a generalized, consistent terminal interface. Use it to promote configuration from staging to production, validate that your apps are set up correctly before a release, script a repeatable setup, or explore an API interactively with common conventions. For developers who live in the terminal, it means one management interface across your configured services, a logical set of commands, and configuration you can export, version, and share.&lt;/p&gt;
&lt;p&gt;This article is an introduction and will highlight what Ping CLI is, what you can do with it right now, and where it’s headed.&lt;/p&gt;
&lt;h2&gt;Where we’ve come from&lt;/h2&gt;
&lt;p&gt;CLIs at Ping have taken many forms and have evolved incrementally for their individual use cases. As our platform capabilities have expanded and become better integrated, we saw the need for a single terminal experience that allows developers to manage configuration from the terminal, as well as bundle configuration into configuration-as-code packages and reliably deploy those packages to higher environments.&lt;/p&gt;
&lt;p&gt;We started the unified CLI journey with Ping CLI in late 2024 as an evolution of &lt;code&gt;pingctl&lt;/code&gt; and, in its initial experimental releases, you gave us valuable feedback that helped us steer toward our current milestone release.&lt;/p&gt;
&lt;p&gt;Version 1.0 represents our commitment to our developer community: to meet developers where they’re at, whether that’s in the terminal, the web admin consoles, or using agents. The CLI is now fully supported and can be built into workflows with confidence.&lt;/p&gt;
&lt;h2&gt;What it is used for&lt;/h2&gt;
&lt;p&gt;The CLI serves three main use cases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Developers in the terminal&lt;/li&gt;
&lt;li&gt;Deterministic automation and CI/CD&lt;/li&gt;
&lt;li&gt;Headless, AI-first use cases&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here we briefly discuss all three, but keep an eye out for future deep-dive articles.&lt;/p&gt;
&lt;h3&gt;Developers in the terminal&lt;/h3&gt;
&lt;p&gt;The CLI is designed to be intuitive and easy to use for developers that live in the terminal. If you prefer the terminal to the browser for quick (or extensive) configuration changes and shell scripting for rapid prototyping and personal automation, then the CLI presents an efficient alternative to administration in the web console.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; use the CLI’s shell completion to quickly build commands with Tab autocomplete.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Deterministic automation and CI/CD&lt;/h3&gt;
&lt;p&gt;GitOps-based CI/CD is an industry standard method of promoting configuration between environments in an automated, auditable, and highly governed way. Where customers have invested in GitOps CI/CD pipelines, there has to be a repeatable, idempotent way to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Export Ping Identity configuration as code.&lt;/li&gt;
&lt;li&gt;Organize exported configuration as code within the filesystem.&lt;/li&gt;
&lt;li&gt;Version control within source control (such as Git).&lt;/li&gt;
&lt;li&gt;During CI/CD workflow execution, apply that versioned configuration to the next environment in the deployment pipeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is no one-size-fits-all approach to CI/CD, but there are industry-standard patterns of use, so the CLI provides the toolkit to natively integrate with a wide range of CI/CD workflow orchestrators, with both imperative (“these are the actions to take to get to the end state”) and declarative (“this is the end state I want, take whatever actions are needed”) methods of configuration management with the ability to integrate with OpenTelemetry collectors for deep observability.&lt;/p&gt;
&lt;h3&gt;Headless, AI-first use cases&lt;/h3&gt;
&lt;p&gt;Ping CLI is designed to be natively understood by coding and configuration agents. The ability to understand and use CLIs is inherent in the training of many large language and small language (LLM and SLM) models, meaning they know how to read and run CLIs and interpret the output, to determine the actions to take.&lt;/p&gt;
&lt;p&gt;By logically laying out the command structure with brief but effective &lt;code&gt;--help&lt;/code&gt; text, agents can crawl the CLI to be able to compose commands to achieve an outcome. When paired with developer documentation (such as the &lt;a href=&quot;https://docs.pingidentity.com/solution-guides/htg_overview.html&quot;&gt;Developer Use Cases&lt;/a&gt;), the agent can compose commands, with data payloads, to accelerate the development of use cases.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Use the bundled &lt;code&gt;ping-cli-usage&lt;/code&gt; Agent Skill so the agent doesn’t need to crawl the &lt;code&gt;--help&lt;/code&gt; texts to understand how to use the CLI.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Agents can use the Ping CLI to augment the CI/CD and configuration promotion workflows, by exporting configuration, raising requests to change the configuration-as-code within the GitOps pipeline with Pull Requests (as coding agents do today), and orchestrating the CI/CD workflows to reduce repetitive work by DevOps developers and administrators.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Refer to &lt;a href=&quot;https://developer.pingidentity.com/build-with-ai&quot;&gt;Build with AI developer documentation&lt;/a&gt; for more information on how agents can use the Ping CLI alongside Agent Skills and documentation for agents.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;What you can do today&lt;/h2&gt;
&lt;p&gt;We’re on our journey to build out the CLI capability and build a truly unified CLI experience that allows rich management features covering all of Ping Identity’s product capabilities. We’ve started by releasing a solid foundation, with configuration management features for &lt;strong&gt;PingOne&lt;/strong&gt; and universal services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DaVinci&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PingOne MFA&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PingOne Protect&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PingOne Verify&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PingOne Authorize&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PingOne Credentials&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s take a look at some of the new features available in 1.0!&lt;/p&gt;
&lt;h3&gt;Interactive guided setup&lt;/h3&gt;
&lt;p&gt;The Ping CLI now provides a guided setup process. Use &lt;code&gt;pingcli init&lt;/code&gt; to walk you through first-time configuration, including connecting products and services and setting some initial preferred settings.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; If you’re looking to just connect one set of products, each platform has its own guided setup process. To specifically connect PingOne with the guided setup interface, use &lt;code&gt;pingcli pingone init&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt=&quot;Animation of the start of pingcli init command&quot; loading=&quot;lazy&quot; width=&quot;992&quot; height=&quot;720&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/init.DRSC2jIP_6zp1.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/init.DRSC2jIP_Z13mCpm.webp 640w, https://developer.pingidentity.com/blog/_astro/init.DRSC2jIP_t0VKH.webp 750w, https://developer.pingidentity.com/blog/_astro/init.DRSC2jIP_Z2p2ut3.webp 828w, https://developer.pingidentity.com/blog/_astro/init.DRSC2jIP_6zp1.webp 992w&quot; /&gt;&amp;gt;
&lt;em&gt;The &lt;code&gt;pingcli init&lt;/code&gt; command&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Authenticate across multiple services&lt;/h3&gt;
&lt;p&gt;We’ve streamlined the login process for developers using interactive logins using the browser. After connecting products to the CLI through the interactive setup, login for all can be performed with a single command.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Animation of the start of pingcli auth login command&quot; loading=&quot;lazy&quot; width=&quot;992&quot; height=&quot;720&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/auth.HQhEAIas_HqLKa.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/auth.HQhEAIas_Zl2q4d.webp 640w, https://developer.pingidentity.com/blog/_astro/auth.HQhEAIas_1bl96Q.webp 750w, https://developer.pingidentity.com/blog/_astro/auth.HQhEAIas_Z1GHi7T.webp 828w, https://developer.pingidentity.com/blog/_astro/auth.HQhEAIas_HqLKa.webp 992w&quot; /&gt;&amp;gt;
&lt;em&gt;The &lt;code&gt;pingcli auth login&lt;/code&gt; command&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The simplicity of managing admin sessions, as one CLI session, allows developers to spend less time authenticating and more time getting the job done.&lt;/p&gt;
&lt;p&gt;Ping CLI supports the login methods you’d expect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service accounts for automation&lt;/strong&gt;: for use with CI/CD pipelines and fully automated workflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactive login flows with silent token refresh&lt;/strong&gt;: where supported, interactive logins are for developers using the CLI directly in the terminal. IAM administrators can apply strong policies to CLI users, including the use of admin MFA.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Where available, the CLI stores tokens safely in your OS keychain, or ephemerally where keychains are not available (such as when running in an OCI container).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Each platform has its own auth management. If you’re looking to just manage PingOne without any other connected product, use &lt;code&gt;pingcli pingone auth login&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Manage configuration across products and services&lt;/h3&gt;
&lt;p&gt;Each product and service gets its own command namespace. PingOne lives under &lt;code&gt;pingcli pingone&lt;/code&gt;, and each resource exposes a consistent set of verbs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;create&lt;/code&gt;: to create new resources within an environment&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apply&lt;/code&gt;: an idempotent way of maintaining a resource’s configuration&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get&lt;/code&gt;: to return the full configuration of a resource by its identifier&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list&lt;/code&gt;: to return multiple resources of the same type (with a filter, where the API allows)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;replace&lt;/code&gt;: to overwrite a resource with new configuration&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delete&lt;/code&gt;: to remove a resource from the environment&lt;/li&gt;
&lt;li&gt;&lt;code&gt;template&lt;/code&gt;: generates a JSON body skeleton as a starting point for manual configuration-as-code development&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Most commands have aliases to make life easier, giving users and agents command patterns that suit them best. For example, &lt;code&gt;pingcli p1&lt;/code&gt; is an alias of &lt;code&gt;pingcli pingone&lt;/code&gt;. The &lt;code&gt;create&lt;/code&gt; verb also has an alias of &lt;code&gt;new&lt;/code&gt;, the &lt;code&gt;update&lt;/code&gt; verb has an alias of &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt; has an alias of &lt;code&gt;ls&lt;/code&gt;, among others. Try out what suits you. If we miss an obvious command alias, tell us with the &lt;code&gt;pingcli feedback&lt;/code&gt; command.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt=&quot;Animation showing pingcli pingone applications get and pingcli pingone application list commands&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/apps.CrOvpQJy_SixUn.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/apps.CrOvpQJy_Ld3Ok.webp 640w, https://developer.pingidentity.com/blog/_astro/apps.CrOvpQJy_1LD8rW.webp 750w, https://developer.pingidentity.com/blog/_astro/apps.CrOvpQJy_ZLI4Ut.webp 828w, https://developer.pingidentity.com/blog/_astro/apps.CrOvpQJy_Z1T8WMd.webp 1080w, https://developer.pingidentity.com/blog/_astro/apps.CrOvpQJy_SixUn.webp 1280w&quot; /&gt;&amp;gt;
&lt;em&gt;The &lt;code&gt;pingcli pingone application&lt;/code&gt; example commands&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The universal services (MFA, Verify, Protect, Credentials, Authorize, and DaVinci) are addressable directly by service, or as part of their platform for convenience:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Animation showing pingcli connector commands, showing that pingcli davinci flows and pingcli pingone davinci flows are equivalent.&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/connector_commands.D_UBLKnQ_Z1mUlVD.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/connector_commands.D_UBLKnQ_Z1bXKd5.webp 640w, https://developer.pingidentity.com/blog/_astro/connector_commands.D_UBLKnQ_cxOfS.webp 750w, https://developer.pingidentity.com/blog/_astro/connector_commands.D_UBLKnQ_1nEinf.webp 828w, https://developer.pingidentity.com/blog/_astro/connector_commands.D_UBLKnQ_1TIN8g.webp 1080w, https://developer.pingidentity.com/blog/_astro/connector_commands.D_UBLKnQ_Z1mUlVD.webp 1280w&quot; /&gt;&amp;gt;
&lt;em&gt;Example showing how DaVinci flows are managed&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The same command shapes carry across resources, services, and products, so the muscle memory you build in one place transfers everywhere. Resources deep-nest naturally too:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pingcli&lt;/span&gt;&lt;span&gt; pingone&lt;/span&gt;&lt;span&gt; applications&lt;/span&gt;&lt;span&gt; sop-assignments&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;span&gt; --environment-id&lt;/span&gt;&lt;span&gt; $ENVIRONMENT_ID &lt;/span&gt;&lt;span&gt;--application-id&lt;/span&gt;&lt;span&gt; $APPLICATION_ID&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pingcli&lt;/span&gt;&lt;span&gt; pingone&lt;/span&gt;&lt;span&gt; groups&lt;/span&gt;&lt;span&gt; group-nestings&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;span&gt; --environment-id&lt;/span&gt;&lt;span&gt; $ENVIRONMENT_ID &lt;/span&gt;&lt;span&gt;--group-id&lt;/span&gt;&lt;span&gt; $GROUP_ID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Profiles for every environment&lt;/h3&gt;
&lt;p&gt;Real workflows span sandboxes, development, testing, staging, and production. Some users will need to be operating on multiple environments, or switching contexts depending on what is needed in the moment.&lt;/p&gt;
&lt;p&gt;Configuration profiles let you keep those configurations side by side and allows quick switching between them globally or per command:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Animation showing pingcli config profiles commands, showing that profiles can be created, selected as the default and overridden with the -P parameter.&quot; loading=&quot;lazy&quot; width=&quot;992&quot; height=&quot;720&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/config_profiles.D97PGcfa_Z1ypd6f.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/config_profiles.D97PGcfa_SfByo.webp 640w, https://developer.pingidentity.com/blog/_astro/config_profiles.D97PGcfa_ykzRw.webp 750w, https://developer.pingidentity.com/blog/_astro/config_profiles.D97PGcfa_ZBBzWl.webp 828w, https://developer.pingidentity.com/blog/_astro/config_profiles.D97PGcfa_Z1ypd6f.webp 992w&quot; /&gt;&amp;gt;
&lt;em&gt;Example showing configuration profile use&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Configuration is persisted in (and can be loaded by) a readable YAML file, and every setting can be overridden by an environment variable, which is ideal for easily injecting configuration and secrets into CI/CD pipelines at runtime.&lt;/p&gt;
&lt;h3&gt;Make raw API calls when you need them&lt;/h3&gt;
&lt;p&gt;The managed resources cover the common cases and are being built out over the next few releases, but sometimes you need the full surface of an API. This is especially useful if you’re using a prerelease product feature that’s not yet built in as a native command, or you need more control than a native resource command can provide.&lt;/p&gt;
&lt;p&gt;Each product exposes an &lt;code&gt;api&lt;/code&gt; command giving you a cURL-like experience with authentication, base URL, and environment details filled in for you automatically:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Animation showing pingcli pingone api commands, showing that pingcli pingone api can be used to select environment details and create resources, with output filtered with jq.&quot; loading=&quot;lazy&quot; width=&quot;992&quot; height=&quot;720&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/api.DBRcnoJo_Z19fOwE.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/api.DBRcnoJo_1uEuuY.webp 640w, https://developer.pingidentity.com/blog/_astro/api.DBRcnoJo_17DGLh.webp 750w, https://developer.pingidentity.com/blog/_astro/api.DBRcnoJo_1itUa9.webp 828w, https://developer.pingidentity.com/blog/_astro/api.DBRcnoJo_Z19fOwE.webp 992w&quot; /&gt;&amp;gt;
&lt;em&gt;Example showing custom API commands&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Output built for scripting&lt;/h3&gt;
&lt;p&gt;Ping CLI speaks both human and machine. Every command accepts &lt;code&gt;-O/--output-format&lt;/code&gt; with four modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;text&lt;/code&gt;: The default, formatted for reading at the terminal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;json&lt;/code&gt;: A single structured document, ideal for &lt;code&gt;jq&lt;/code&gt; or JMESPath queries using the &lt;code&gt;--query&lt;/code&gt; parameter.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ndjson&lt;/code&gt;: Newline-delimited JSON (one record per line), ideal for &lt;code&gt;jq&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, and for streaming and sending to log pipelines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ndjson-typed&lt;/code&gt;: Newline-delimited JSON with type metadata per record, ideal for &lt;code&gt;jq&lt;/code&gt; and sending to log pipelines that need a consistent structure.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For extracting exactly the fields you need, &lt;code&gt;--query&lt;/code&gt; takes a JMESPath expression directly rather than piping to another tool. For transformations that go beyond what JMESPath supports, pipe to &lt;code&gt;jq&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Animation showing pingcli outputs using the --query parameter and piping to jq.&quot; loading=&quot;lazy&quot; width=&quot;992&quot; height=&quot;720&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/output_formats.kaox07Hc_Z7500n.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/output_formats.kaox07Hc_Z26qDhT.webp 640w, https://developer.pingidentity.com/blog/_astro/output_formats.kaox07Hc_Z235dzR.webp 750w, https://developer.pingidentity.com/blog/_astro/output_formats.kaox07Hc_2rh1Om.webp 828w, https://developer.pingidentity.com/blog/_astro/output_formats.kaox07Hc_Z7500n.webp 992w&quot; /&gt;&amp;gt;
&lt;em&gt;Using the &lt;code&gt;--query&lt;/code&gt; parameter and piping output to &lt;code&gt;jq&lt;/code&gt; example&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For pipelines that must react to results, &lt;code&gt;--no-color&lt;/code&gt; keeps logs clean, while &lt;code&gt;--detailed-exitcode&lt;/code&gt; distinguishes success (&lt;code&gt;0&lt;/code&gt;), error (&lt;code&gt;1&lt;/code&gt;), and success-with-warnings (&lt;code&gt;2&lt;/code&gt;) so a CI step can branch on the outcome as well as text.&lt;/p&gt;
&lt;h3&gt;Quality-of-life extras&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shell completion&lt;/strong&gt;: &lt;code&gt;pingcli completion zsh&lt;/code&gt; (also &lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;fish&lt;/code&gt;, &lt;code&gt;powershell&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI agent skills&lt;/strong&gt;: &lt;code&gt;pingcli agent-skills install&lt;/code&gt; / &lt;code&gt;list&lt;/code&gt; for teams building agent-assisted workflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenTelemetry integration&lt;/strong&gt;: For deep observability into CLI actions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;Ping CLI ships as a signed release for macOS, Linux, and Windows across common architectures, and as a Docker image. To get started for PingOne:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Follow the PingOne getting started guide in the &lt;a href=&quot;https://developer.pingidentity.com/pingcli/latest/pingone/getting-started.html&quot;&gt;CLI documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Run the guided initialization walkthrough (which authenticates you at the end): &lt;code&gt;pingcli init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;List the environments you’re able to view: &lt;code&gt;pingcli pingone environments list&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Full installation instructions, the complete command reference, and configuration guides live on the &lt;a href=&quot;https://developer.pingidentity.com/pingcli/&quot;&gt;Ping Identity Developer Portal&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What’s next&lt;/h2&gt;
&lt;p&gt;Version 1.0 is a commitment to stability: a foundation you can build automation on with confidence. But it’s a milestone, not a destination. The vision is a genuinely universal tool, one that speaks to every Ping service and platform with the same fluency it speaks to PingOne today.&lt;/p&gt;
&lt;p&gt;As a preview, in the pipeline we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Advanced Identity Cloud configuration management.&lt;/li&gt;
&lt;li&gt;PingFederate configuration management (for both private tenant and software).&lt;/li&gt;
&lt;li&gt;Defined declarative import/export tools.&lt;/li&gt;
&lt;li&gt;Refined processes for exporting and generating configuration-as-code (as an evolution of the experimental &lt;code&gt;platform export&lt;/code&gt; command in Ping CLI version 0.8).&lt;/li&gt;
&lt;li&gt;Enhanced headless AI features, enabling more streamlined use with configuration agents.&lt;/li&gt;
&lt;li&gt;The ability to introspect environments and debug issues.&lt;/li&gt;
&lt;li&gt;The ability to extend with custom plugins.
And more to come.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Can we improve? Your feedback matters&lt;/h2&gt;
&lt;p&gt;Keep an eye out for future releases that enhance the capabilities of the CLI even further.
Your real-world feedback matters. As you use the Ping CLI, tell us what you need.&lt;/p&gt;
&lt;p&gt;Is there a command alias you’re finding is missing? Is there a feature you’d like included? Is there a use case we can improve upon?&lt;/p&gt;
&lt;p&gt;Run &lt;code&gt;pingcli feedback&lt;/code&gt; to reach us directly, or &lt;a href=&quot;https://github.com/pingidentity/pingcli/issues&quot;&gt;open an issue on GitHub&lt;/a&gt; to track a request or vote on features.&lt;/p&gt;
&lt;h2&gt;And finally&lt;/h2&gt;
&lt;p&gt;Look out for upcoming articles where we deep dive into Ping CLI with headless, AI-first configuration using agents, building a CI/CD workflow with Ping CLI, and generating configuration-as-code. Welcome to Ping CLI 1.0!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Used the CLI to build something great? Tell the community! Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.CgtFtVRF_ZIPsNS.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Multi-Brand administration in PingOne with new group-level role assignments</title><link>https://developer.pingidentity.com/blog/introducing-pingone-administrator-group-role-assignments/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/introducing-pingone-administrator-group-role-assignments/</guid><description>How group-level role assignments in PingOne solve delegated administration challenges for multi-brand consumer identity deployments.</description><pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As you scale PingOne to support richer, multi-brand experiences, a familiar question tends to pop up: &lt;strong&gt;how do you delegate administration cleanly when the same end user belongs to many different business lines?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this post, we’ll walk through what &lt;strong&gt;group-level assignments of administrative roles&lt;/strong&gt; are, how they behave in PingOne, and the kinds of real-world problems they’re designed to solve.&lt;/p&gt;
&lt;h2&gt;Background: How PingOne administration worked before group level assignment&lt;/h2&gt;
&lt;p&gt;PingOne uses a hierarchy of &lt;strong&gt;organization → environment → population / environment → application&lt;/strong&gt; to determine administration over different resources as shown below, before the new group-level assignment type.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A tree diagram showing the PingOne administration hierarchy, with the Administrators Environment connected by dashed role-assignment arrows to an Organization node that branches into Environment A (with Population and Application) and Environment B (with Population and Application)&quot; loading=&quot;lazy&quot; width=&quot;1350&quot; height=&quot;870&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/pingone-administration-hierarchy-diagram.CzRhxec-_Z2jgWdr.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/pingone-administration-hierarchy-diagram.CzRhxec-_1FUEtJ.webp 640w, https://developer.pingidentity.com/blog/_astro/pingone-administration-hierarchy-diagram.CzRhxec-_Z2oUNxa.webp 750w, https://developer.pingidentity.com/blog/_astro/pingone-administration-hierarchy-diagram.CzRhxec-_Z1YrYSJ.webp 828w, https://developer.pingidentity.com/blog/_astro/pingone-administration-hierarchy-diagram.CzRhxec-_Z1j5tYV.webp 1080w, https://developer.pingidentity.com/blog/_astro/pingone-administration-hierarchy-diagram.CzRhxec-_1PWFUI.webp 1280w, https://developer.pingidentity.com/blog/_astro/pingone-administration-hierarchy-diagram.CzRhxec-_Z2jgWdr.webp 1350w&quot; /&gt;&amp;gt;
&lt;em&gt;PingOne administration hierarchy — role assignments before group-level support&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A few important concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Administrator environment&lt;/strong&gt;: Each organization comes with a pre-created “administrators” environment. This is where we recommend managing platform administrators, separate from end users. (There are cases where delegated admins should live alongside end users)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in vs custom roles&lt;/strong&gt;: Built-in roles cover common platform responsibilities (for example, Identity Data Admin and Help Desk Admin), while custom roles let you combine granular permissions to reach least privilege.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Levels&lt;/strong&gt;: Roles can be assigned at different levels (organization, environment, population, and — now — groups) so that an admin’s power is limited to exactly what they should manage. This can be done across environments so that a single admin identity has roles over other environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Today, &lt;strong&gt;population-level roles&lt;/strong&gt; are the main way to segment administrator access to different user communities (for example, employees and contractors, or region A and region B) where a user can only exist in a single population. That works well for many B2B and workforce scenarios, but it starts to show its limits in some high-scale consumer use cases.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Learn more in &lt;a href=&quot;https://developer.pingidentity.com/blog/delegated-administration-in-pingone/&quot;&gt;Delegated Administration in PingOne&lt;/a&gt; for a full background on delegated admin and a breakdown of Populations and Groups in PingOne.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The customer problem: shared identity, many brands&lt;/h2&gt;
&lt;p&gt;If a large media company runs many consumer applications under different sub-brands, each brand has its own app and each app has its &lt;strong&gt;own help desk&lt;/strong&gt;, but &lt;strong&gt;end users sign on with a single shared credential across all of them&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;They need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let each brand’s help desk reset passwords, manage MFA devices, and troubleshoot issues &lt;strong&gt;only for their brand’s users&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Avoid creating &lt;strong&gt;separate identities per brand&lt;/strong&gt;, because the user experience should be “one login, many apps”.&lt;/li&gt;
&lt;li&gt;Avoid duplicating users into separate populations just to drive administration, because users commonly span multiple brands and populations aren’t designed for this overlap.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words, they need &lt;strong&gt;delegated administration segmented by groups&lt;/strong&gt;, not just by populations.&lt;/p&gt;
&lt;h2&gt;How group-level roles behave&lt;/h2&gt;
&lt;p&gt;With group-level assignment, the goal is for the model to feel predictable and intuitive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If an admin has a role &lt;strong&gt;over a population&lt;/strong&gt;, and a user belongs to that population, the admin can manage that user — just like today.&lt;/li&gt;
&lt;li&gt;If an admin has a role &lt;strong&gt;over a group&lt;/strong&gt;, and the user is in that group, the admin can manage that user — even if the user is also in other groups or populations.&lt;/li&gt;
&lt;li&gt;If the admin has &lt;strong&gt;both&lt;/strong&gt; population and group-level assignments, the effective access is &lt;strong&gt;the union&lt;/strong&gt;: if the user matches &lt;em&gt;either&lt;/em&gt; level, the admin can manage them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Practically speaking: &lt;strong&gt;it’s enough for the admin to have an assignment at either the population level or group level to manage a given user&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is exactly what customers like the multi-brand help desk scenario need: “if this user is in any of &lt;em&gt;my&lt;/em&gt; brand groups, I should be able to help them, even if they also belong to other brands.”&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A tree diagram extending the PingOne hierarchy to include Group nodes (marked &apos;new&apos;) under Population in both Environment A and Environment B, with dashed orange arrows indicating the new group-level role assignments from the Administrators Environment&quot; loading=&quot;lazy&quot; width=&quot;1500&quot; height=&quot;960&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/group-level-roles-diagram.CM_ocrSU_Z17oQdy.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/group-level-roles-diagram.CM_ocrSU_Zam2M7.webp 640w, https://developer.pingidentity.com/blog/_astro/group-level-roles-diagram.CM_ocrSU_ZV38Qm.webp 750w, https://developer.pingidentity.com/blog/_astro/group-level-roles-diagram.CM_ocrSU_ZiXAVy.webp 828w, https://developer.pingidentity.com/blog/_astro/group-level-roles-diagram.CM_ocrSU_Vhspk.webp 1080w, https://developer.pingidentity.com/blog/_astro/group-level-roles-diagram.CM_ocrSU_ZxGGmK.webp 1280w, https://developer.pingidentity.com/blog/_astro/group-level-roles-diagram.CM_ocrSU_Z17oQdy.webp 1500w&quot; /&gt;&amp;gt;
&lt;em&gt;PingOne administration hierarchy with group-level role assignment support added&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Tackling user creation&lt;/h2&gt;
&lt;p&gt;Group-level assignments introduced a chicken and egg problem: how can an admin create a user and then manage them when in PingOne you cannot assign a user to a group at creation time?&lt;/p&gt;
&lt;p&gt;To solve this problem, we’ve introduced the ability to assign a user to a static group when creating them. This ensures that if an admin needs to create a user manually, they can do so and still manage them afterwards.&lt;/p&gt;
&lt;p&gt;There are some caveats to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If an admin creates a user in a group (given that they have a role assignment over that group) that is associated with a population, the user is created in that population.&lt;/li&gt;
&lt;li&gt;If an admin created a user in a group not associated with a population, then the user is created in the default population if there is one.
&lt;ul&gt;
&lt;li&gt;If there is no default population (not recommended) and the admin is in the same environment, the user is created in the same population as the admin.&lt;/li&gt;
&lt;li&gt;In the event the admin is not in the same environment, user creation will fail.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In most cases the above isn’t a problem, but it is important to understand in more complex organizations.&lt;/p&gt;
&lt;h2&gt;Some example scenarios&lt;/h2&gt;
&lt;p&gt;With group-level assignments, you can model administration much more closely to how your business is structured.&lt;/p&gt;
&lt;h3&gt;1. Brand help desks&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A media company runs multiple fan sites and apps under different artist or label brands. Each brand has its own help desk team, but fans use the same account everywhere.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A flow diagram showing a single End User identity connecting to Brand A App, Brand B App, and Brand C App, each with their own Help Desk team, all sharing one Population containing Brand A Users, Brand B Users, and Brand C Users groups&quot; loading=&quot;lazy&quot; width=&quot;1440&quot; height=&quot;645&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/brand-help-desk-setup.D2lpnFq3_G2iEl.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/brand-help-desk-setup.D2lpnFq3_Z537L8.webp 640w, https://developer.pingidentity.com/blog/_astro/brand-help-desk-setup.D2lpnFq3_Z26vvnm.webp 750w, https://developer.pingidentity.com/blog/_astro/brand-help-desk-setup.D2lpnFq3_Z2jgW5C.webp 828w, https://developer.pingidentity.com/blog/_astro/brand-help-desk-setup.D2lpnFq3_2qnGzc.webp 1080w, https://developer.pingidentity.com/blog/_astro/brand-help-desk-setup.D2lpnFq3_9IR1o.webp 1280w, https://developer.pingidentity.com/blog/_astro/brand-help-desk-setup.D2lpnFq3_G2iEl.webp 1440w&quot; /&gt;&amp;gt;
&lt;em&gt;The multi-brand identity problem — one user identity, multiple brand help desks, separate support boundaries needed&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to set it up&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a group for each brand (or set of brands) your support team owns. For example, &lt;code&gt;Brand A Users&lt;/code&gt;, &lt;code&gt;Brand B Users&lt;/code&gt;, &lt;code&gt;VIP Fans&lt;/code&gt;, and so on.&lt;/li&gt;
&lt;li&gt;Assign a &lt;strong&gt;Help Desk Admin&lt;/strong&gt; (or equivalent custom role) at the &lt;code&gt;Brand A Users&lt;/code&gt; Group level and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;A diagram showing Help Desk A Admin with a role assignment over the Brand A Users Group, and Help Desk B Admin with no assignment. User Alex is a member of Brand A Users Group, Brand B Users Group, and VIP Users Group. A green checkmark shows Help Desk A can manage Alex; a red cross shows Help Desk B cannot.&quot; loading=&quot;lazy&quot; width=&quot;1530&quot; height=&quot;990&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/brand-help-desk-role-assignment.C7gcw_rh_Z1qVrq9.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/brand-help-desk-role-assignment.C7gcw_rh_ZthkuE.webp 640w, https://developer.pingidentity.com/blog/_astro/brand-help-desk-role-assignment.C7gcw_rh_1Mg4At.webp 750w, https://developer.pingidentity.com/blog/_astro/brand-help-desk-role-assignment.C7gcw_rh_1nasKf.webp 828w, https://developer.pingidentity.com/blog/_astro/brand-help-desk-role-assignment.C7gcw_rh_Z1jJrUW.webp 1080w, https://developer.pingidentity.com/blog/_astro/brand-help-desk-role-assignment.C7gcw_rh_oqhuX.webp 1280w, https://developer.pingidentity.com/blog/_astro/brand-help-desk-role-assignment.C7gcw_rh_Z1qVrq9.webp 1530w&quot; /&gt;&amp;gt;
&lt;em&gt;Brand Help Desk access via group roles — Help Desk A can manage Alex through a group-scoped role; Help Desk B has no assignment and cannot&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The Brand A help desk can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reset passwords&lt;/li&gt;
&lt;li&gt;Manage MFA devices&lt;/li&gt;
&lt;li&gt;Perform recovery actions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;only&lt;/strong&gt; for users in the Brand A group — even if those users also belong to other brands or global groups.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This lets you keep a single, clean user identity while still giving each support team exactly the access they need.&lt;/p&gt;
&lt;h3&gt;2. Specialized admin teams&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You have a small, expert team that handles high-risk operations, such as managing MFA devices for executives or handling escalated recovery flows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to set it up&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create groups like &lt;code&gt;Executives&lt;/code&gt;, &lt;code&gt;High-Risk Accounts&lt;/code&gt;, or &lt;code&gt;Escalated Support&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build a narrow custom role focused on the operations this team is allowed to perform (for example, &lt;strong&gt;MFA device management&lt;/strong&gt; and &lt;strong&gt;account recovery&lt;/strong&gt; only).&lt;/li&gt;
&lt;li&gt;Assign that custom role at the high-risk group level.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The specialized team can perform sensitive operations only for the users you’ve explicitly grouped, without granting those permissions over the entire population or environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Designing your model with group-level assignments&lt;/h2&gt;
&lt;p&gt;When you’re planning how to use group-level assignments, a few guiding questions help:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;How do I delegate based on my organizational structure?&lt;/strong&gt;
Think of your organizational structure and goals first, then align administration to it using groups and populations for finer delegation and environment or organization for those needing full visibility and control.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What’s the “home” for my admins?&lt;/strong&gt;
Generally continue to use the administrators environment as the home for admin identities, and then assign roles over the right mix of environments, populations, and groups from there. If your delegated admins are also end users, consider keeping them in the production environment rather than separate. Think about how you want to separate or combine the administrative responsibilities for a person from their end user identity.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The end result is an administration model that reflects how your teams actually work day to day, while still keeping a single, consistent identity for every user.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do you have thoughts or questions on this article? Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.5uUWCk_D_ZlpQmJ.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Securing ChatGPT apps with OAuth 2.0 and CIBA</title><link>https://developer.pingidentity.com/blog/securing-chatgpt-apps/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/securing-chatgpt-apps/</guid><description>How to secure a ChatGPT app with Ping Identity, OAuth, PingOne Authorize, OAuth Token Exchange, and CIBA.</description><pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This post was originally published on &lt;a href=&quot;https://carbonefederico.github.io/blog-content/posts/securing-chatgpt-apps/&quot;&gt;Federico Carbone’s blog site&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Context&lt;/h2&gt;
&lt;p&gt;Enterprises are now building apps and tools for personal agents, and those agents rarely operate in a single trust model. Some capabilities need access to public or low-friction resources. Others need the agent to act on behalf of a signed-in user to reach private data or protected APIs. Some actions go further and require explicit user approval before the agent can complete them.&lt;/p&gt;
&lt;p&gt;In this blog, I describe how the PingOne platform can be used to secure a personal agent through a demo ChatGPT hotel booking app that uses OAuth 2.0, PingOne Authorize, Token Exchange, and CIBA to move from public access to authenticated access and then to transaction approval.&lt;/p&gt;
&lt;h2&gt;MyHotels demo&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;MyHotels&lt;/code&gt; is a demo ChatGPT app built with the &lt;a href=&quot;https://developers.openai.com/apps-sdk&quot;&gt;OpenAI Apps SDK&lt;/a&gt;. It lets a user search for hotels, ask for member-only pricing, and start a booking that requires end-user approval before it completes. I use this demo application to show how Ping Identity can protect a ChatGPT personal agent when that agent needs to move across different trust levels.&lt;/p&gt;
&lt;p&gt;The following diagram shows the high-level logical architecture for the demo.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;%%{init: {&apos;flowchart&apos;: {&apos;curve&apos;: &apos;linear&apos;}}}%%&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    U[&quot;User&quot;] --&amp;gt; CG&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    U --&amp;gt; P1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    subgraph CG[&quot;ChatGPT Host Runtime&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        direction TB&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        CORE[&quot;ChatGPT&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        WR[&quot;Widget Runtime\n(runs Widget UI)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    subgraph MCP[&quot;MCP Server&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        direction TB&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        TOOLS[&quot;Tools&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        RES[&quot;Resources\n(publishes Widget UI)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    API[&quot;Backend API&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    P1[&quot;PingOne&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CORE --&amp;gt; TOOLS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CORE --&amp;gt; RES&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CORE --&amp;gt; WR&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CORE --&amp;gt; P1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP --&amp;gt; API&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP --&amp;gt; P1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;MyHotels logical architecture&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User: interacts with ChatGPT and the mounted widget to search hotels, request member rates, and approve or finalize booking actions. The user also signs in and completes approval prompts through PingOne.&lt;/li&gt;
&lt;li&gt;ChatGPT Host Runtime: discovers MCP tools, resources, and authentication requirements; calls MCP tools on behalf of the user; reads widget resources; mounts the widget in its sandboxed runtime; and uses PingOne during the OAuth flow for protected MCP tools.&lt;/li&gt;
&lt;li&gt;Widget Runtime: runs the MyHotels widget UI inside ChatGPT and calls tools back through ChatGPT.&lt;/li&gt;
&lt;li&gt;MCP Server: exposes the hotel tools, publishes the widget HTML as an MCP resource, calls PingOne Authorize for policy decisions, exchanges tokens for backend API access, starts CIBA approval when policy requires end-user consent, and calls the backend hotel API.&lt;/li&gt;
&lt;li&gt;Backend API: owns the demo hotel business surface, including the hotel catalog, member-rate access, booking quotes, booking intents, and mocked booking confirmation.&lt;/li&gt;
&lt;li&gt;PingOne: provides the identity and authorization services used by the demo, including agent registration, user authentication, token exchange, policy decisions, and CIBA-based end-user approval.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;End user experience&lt;/h2&gt;
&lt;p&gt;The following videos demonstrate the end-user experience. First, the no-session journey shows what happens when the user starts from a fresh ChatGPT conversation and crosses from a public flow into a protected one. Second, the existing-session journey shows the same general flow when the user already has an authenticated session and can move more quickly into protected actions:&lt;/p&gt;
&lt;p&gt;For this demo, payment details are assumed to already be on file with &lt;code&gt;MyHotels&lt;/code&gt;. ChatGPT does not collect or submit card data. After the required authorization checks are satisfied, the backend booking service is the component that completes the mocked payment and confirms the booking.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No-session journey:&lt;/strong&gt;&lt;/p&gt;

  
  Your browser does not support the video tag.

&lt;p&gt;&lt;strong&gt;Existing-session journey:&lt;/strong&gt;&lt;/p&gt;

  
  Your browser does not support the video tag.

&lt;p&gt;The following video shows the booking flow when the policy uses transaction thresholds: bookings below &lt;code&gt;200 EUR&lt;/code&gt; proceed without additional approval, bookings above &lt;code&gt;200 EUR&lt;/code&gt; require user approval, and bookings above &lt;code&gt;1000 EUR&lt;/code&gt; are denied.&lt;/p&gt;

  
  Your browser does not support the video tag.

&lt;h2&gt;How ChatGPT apps work&lt;/h2&gt;
&lt;p&gt;At a high-level, ChatGPT is the host runtime, and the demo app exposes capabilities through MCP tools and resources. When ChatGPT decides to call a tool such as hotel search, it sends the tool request to the MCP server. When a tool response includes an output template, ChatGPT reads the matching resource and mounts the widget UI inside its own runtime.&lt;/p&gt;
&lt;p&gt;The tool surface is small and intentional:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;search_hotels&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search_hotels_member_rates&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prepare_booking&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finalize_booking&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The widget is delivered as the MCP resource &lt;code&gt;ui://widget/myhotels-widget.html&lt;/code&gt;. The widget is not just a public web page that ChatGPT opens. It is a resource served through MCP and mounted by ChatGPT when a tool response references it through &lt;code&gt;openai/outputTemplate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After mounting, the widget uses the &lt;code&gt;window.openai&lt;/code&gt; bridge to interact with ChatGPT. In this project, that bridge is used in two different ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sendFollowUpMessage(...)&lt;/code&gt; to ask ChatGPT to call the protected member-rates tool&lt;/li&gt;
&lt;li&gt;&lt;code&gt;callTool(...)&lt;/code&gt; to invoke booking-related tools directly from the widget&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That gives the app a useful split:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ChatGPT remains the orchestrator&lt;/li&gt;
&lt;li&gt;the widget becomes the interactive UI surface&lt;/li&gt;
&lt;li&gt;the backend systems stay behind the MCP boundary&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Identity Provider&lt;/h2&gt;
&lt;p&gt;At a high-level, Ping Identity plays four different roles in this demo. First, PingOne supports user authentication for ChatGPT when the app crosses into protected functionality. Second, the MCP server uses PingOne Authorize to evaluate each tool call against policy. Third, the MCP server uses OAuth token exchange to obtain a backend API token with the correct audience and scope. Fourth, PingOne supports the CIBA approval flow used by the MCP server for booking approval:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PingOne handles user sign-in&lt;/li&gt;
&lt;li&gt;PingOne Authorize decides whether the tool call should be allowed&lt;/li&gt;
&lt;li&gt;token exchange gives the backend API a token minted for its own resource&lt;/li&gt;
&lt;li&gt;CIBA handles higher-friction approval when policy requires it&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;PingOne Authorize&lt;/h2&gt;
&lt;p&gt;The current demo does not rely only on OAuth scopes at the MCP layer. Every tool call is sent to PingOne Authorize with a payload that identifies the MCP service, the requested tool, selected request parameters, and the inbound ChatGPT bearer token, so that PingOne Authorize can decide whether the tool call should be allowed.&lt;/p&gt;
&lt;p&gt;That lets the MCP server enforce a mixed model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;search_hotels&lt;/code&gt; remains available to unauthenticated users so the public hotel catalog can be searched without sign-in&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search_hotels_member_rates&lt;/code&gt; is allowed only when the request is initiated by a user who is allowed to access ChatGPT, the actor context shows the ChatGPT connector acting on behalf of that user, the token is intended for the MCP resource, and the token carries the correct scope&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prepare_booking&lt;/code&gt; is allowed only when the same user, actor, audience, and MCP booking-scope checks pass, and the decision also evaluates quote attributes such as &lt;code&gt;totalPrice&lt;/code&gt; and &lt;code&gt;currency&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finalize_booking&lt;/code&gt; is allowed only when the same protected-user, actor, audience, and scope checks pass&lt;/li&gt;
&lt;li&gt;any MCP request that does not satisfy one of those allowed patterns is denied&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For booking, the important point is that policy is not binary. The quote amount changes the outcome:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;below &lt;code&gt;200 EUR&lt;/code&gt;: permit&lt;/li&gt;
&lt;li&gt;above &lt;code&gt;200 EUR&lt;/code&gt;: permit with a human-in-the-loop obligation&lt;/li&gt;
&lt;li&gt;above &lt;code&gt;1000 EUR&lt;/code&gt;: deny&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So in the current design, PingOne Authorize decides whether booking is allowed immediately, requires explicit user approval, or should be blocked entirely.&lt;/p&gt;
&lt;h2&gt;Token exchange&lt;/h2&gt;
&lt;p&gt;The ChatGPT-facing token is meant for the MCP surface. The backend API is a different protected resource with its own audience and scopes. Instead of forwarding the original token and overloading its meaning, the MCP server exchanges it for a backend API token.&lt;/p&gt;
&lt;p&gt;This gives us clearer boundaries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ChatGPT gets a token for the MCP resource&lt;/li&gt;
&lt;li&gt;the MCP server gets a token for the backend API resource&lt;/li&gt;
&lt;li&gt;the backend validates a token that was actually minted for it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the current configuration, the ChatGPT-facing MCP token can look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;aud&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;myhotels-hotelmcp&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;sub&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;user-123&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;given_name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;groups&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;ChatGPT User&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;act&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;sub&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;chatgpt-client-id&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this demo, the exchanged backend token can look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;iss&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://auth.pingone.../as&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;aud&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;myhotels-hotelapi&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;sub&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;user-123&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;scope&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;my-hotels:api:member_rates&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;client_id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mcp-token-exchange-client-id&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;act&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;sub&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mcp-token-exchange-client-id&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;act&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;sub&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;chatgpt-client-id&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are two important things in these tokens.&lt;/p&gt;
&lt;p&gt;First, the token audiences and scopes stay separated. The ChatGPT-facing token is for the MCP resource. The exchanged token is for the backend API and carries one of the backend API scopes such as &lt;code&gt;my-hotels:api:member_rates&lt;/code&gt; or &lt;code&gt;my-hotels:api:book&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Second, the token preserves an actor chain. The first token already identifies the ChatGPT client as the acting party. After exchange, the subject is still the user, &lt;code&gt;user-123&lt;/code&gt;, but the token now also shows that the MCP token-exchange client acted on behalf of the original ChatGPT client. That is exactly the kind of traceability we want when a personal agent triggers downstream API calls through an intermediary service.&lt;/p&gt;
&lt;h2&gt;CIBA&lt;/h2&gt;
&lt;p&gt;In many architectures, the client is the component that initiates CIBA. In this demo, we moved it to the MCP server rather than the ChatGPT client. We still wanted approval to be bound to a concrete business transaction, but we do not control the ChatGPT runtime like a conventional first-party client. The MCP layer is therefore the practical control point: it has the user context, it receives the policy obligation from PingOne Authorize, it can initiate approval with PingOne, and it can coordinate the protected call path to the backend API.&lt;/p&gt;
&lt;p&gt;The backend still owns the booking intent and its lifecycle. The MCP server starts CIBA, stores the &lt;code&gt;auth_req_id&lt;/code&gt;, and maps it to the server-side transaction ID returned by the backend. The widget only knows the transaction ID and polls for status updates.&lt;/p&gt;
&lt;h2&gt;The user journeys in detail&lt;/h2&gt;
&lt;h3&gt;1. Public hotel search&lt;/h3&gt;
&lt;p&gt;The simplest journey starts with a natural-language request such as “show me hotels in Milan”.&lt;/p&gt;
&lt;p&gt;ChatGPT calls &lt;code&gt;search_hotels&lt;/code&gt; on the MCP server. The MCP server first asks PingOne Authorize for a policy decision. If the decision is &lt;code&gt;PERMIT&lt;/code&gt;, it returns hotel results and the widget template reference. ChatGPT then reads &lt;code&gt;ui://widget/myhotels-widget.html&lt;/code&gt;, mounts the widget, and passes the tool output into the widget runtime.&lt;/p&gt;
&lt;p&gt;The widget then renders:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a map&lt;/li&gt;
&lt;li&gt;hotel markers&lt;/li&gt;
&lt;li&gt;hotel cards&lt;/li&gt;
&lt;li&gt;standard nightly rates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No authentication is required for this part of the experience.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant U as User&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant CG as ChatGPT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant MCP as MCP Server&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant PDP as PingOne Authorize&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant W as Widget&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    U-&amp;gt;&amp;gt;CG: Search hotels in a city&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CG-&amp;gt;&amp;gt;MCP: tools/call(search_hotels)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP-&amp;gt;&amp;gt;PDP: PDP decision(search_hotels)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    PDP--&amp;gt;&amp;gt;MCP: PERMIT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP--&amp;gt;&amp;gt;CG: hotels + outputTemplate&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CG-&amp;gt;&amp;gt;MCP: resources/read(ui://widget/myhotels-widget.html)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP--&amp;gt;&amp;gt;CG: widget HTML resource&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CG-&amp;gt;&amp;gt;W: instantiate widget runtime&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CG-&amp;gt;&amp;gt;W: provide toolOutput / globals&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    W-&amp;gt;&amp;gt;W: render hotels and map&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Public hotel search flow&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;2. Member-rate search&lt;/h3&gt;
&lt;p&gt;The second journey starts from the rendered widget. The user clicks &lt;code&gt;Show Member Rates&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The widget does not fetch pricing directly from the backend. Instead, it asks ChatGPT to call &lt;code&gt;search_hotels_member_rates&lt;/code&gt;. That tool is protected. If the user is not signed in or does not have the required scope, the MCP server returns an OAuth challenge. ChatGPT handles the sign-in flow and retries the tool call with a valid bearer token.&lt;/p&gt;
&lt;p&gt;After that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the MCP server asks PingOne Authorize for a decision using the inbound bearer token&lt;/li&gt;
&lt;li&gt;PingOne Authorize verifies that the caller is a ChatGPT user and that the MCP token has the required scope&lt;/li&gt;
&lt;li&gt;it performs token exchange to obtain a backend API token&lt;/li&gt;
&lt;li&gt;it calls the backend API with the exchanged token&lt;/li&gt;
&lt;li&gt;it returns hotel results with member pricing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The widget updates in place and shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;discounted member rates&lt;/li&gt;
&lt;li&gt;savings compared to the standard rate&lt;/li&gt;
&lt;li&gt;the authenticated user display name in the UI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a good example of progressive authentication. The user can explore first and authenticate only when the value of signing in is obvious.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant U as User&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant W as Widget&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant CG as ChatGPT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant MCP as MCP Server&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant P1 as Ping Identity&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant PDP as PingOne Authorize&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant API as Backend API&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    U-&amp;gt;&amp;gt;W: Show member rates&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    W-&amp;gt;&amp;gt;CG: window.openai.sendFollowUpMessage(...)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CG-&amp;gt;&amp;gt;MCP: tools/call(search_hotels_member_rates)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP-&amp;gt;&amp;gt;PDP: PDP decision(search_hotels_member_rates)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    alt token missing or insufficient&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        MCP--&amp;gt;&amp;gt;CG: OAuth challenge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        CG-&amp;gt;&amp;gt;P1: OAuth flow&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        P1--&amp;gt;&amp;gt;CG: access token&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        CG-&amp;gt;&amp;gt;MCP: retry tools/call(search_hotels_member_rates)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        MCP-&amp;gt;&amp;gt;PDP: PDP decision(search_hotels_member_rates)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    PDP--&amp;gt;&amp;gt;MCP: PERMIT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP-&amp;gt;&amp;gt;P1: token exchange&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    P1--&amp;gt;&amp;gt;MCP: backend API token&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP-&amp;gt;&amp;gt;API: GET /hotels?memberRates=true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    API--&amp;gt;&amp;gt;MCP: member-rate hotels&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP--&amp;gt;&amp;gt;CG: member-rate hotels&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CG-&amp;gt;&amp;gt;W: provide updated toolOutput&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Member-rate search flow with OAuth and token exchange&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;3. Booking with approval&lt;/h3&gt;
&lt;p&gt;The booking journey starts when the user clicks &lt;code&gt;Book&lt;/code&gt; on a hotel card and submits check-in date and number of nights.&lt;/p&gt;
&lt;p&gt;The widget calls &lt;code&gt;prepare_booking&lt;/code&gt;. That tool is also protected, but the logic is now more nuanced than a simple authenticated-or-not check.&lt;/p&gt;
&lt;p&gt;The MCP first exchanges the token for a backend API token and asks the backend for an authoritative booking quote. It then sends the inbound bearer token, the tool name, and the quote attributes such as &lt;code&gt;totalPrice&lt;/code&gt; and &lt;code&gt;currency&lt;/code&gt; to PingOne Authorize. If policy returns &lt;code&gt;PERMIT&lt;/code&gt;, MCP creates the booking intent. If policy also returns a human-in-the-loop obligation, MCP starts CIBA. If no such obligation is returned, MCP can confirm the booking immediately.&lt;/p&gt;
&lt;p&gt;This is a useful design choice because the approval threshold is policy-driven rather than hard-coded into the app.&lt;/p&gt;
&lt;p&gt;Across the MCP and backend layers, the system stores:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the transaction ID&lt;/li&gt;
&lt;li&gt;the booking owner&lt;/li&gt;
&lt;li&gt;the CIBA &lt;code&gt;auth_req_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the quote and approval state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The widget receives either an immediate confirmation or a pending booking status. For approval-driven bookings, it starts polling &lt;code&gt;finalize_booking&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;From there the flow is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The MCP server asks PingOne Authorize for each finalization attempt.&lt;/li&gt;
&lt;li&gt;It verifies that the caller still matches the booking owner.&lt;/li&gt;
&lt;li&gt;It polls PingOne using the stored &lt;code&gt;auth_req_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When approval reaches &lt;code&gt;approved&lt;/code&gt;, the MCP server exchanges the token again if needed and confirms the backend booking intent.&lt;/li&gt;
&lt;li&gt;The widget reflects the current state in ChatGPT.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So the user interaction is conversational at the start, visual in the middle, and approval-driven at the end.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant U as User&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant W as Widget&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant CG as ChatGPT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant MCP as MCP Server&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant PDP as PingOne Authorize&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant API as Backend API&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    participant P1 as Ping Identity&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    U-&amp;gt;&amp;gt;W: Book hotel&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    W-&amp;gt;&amp;gt;CG: window.openai.callTool(&quot;prepare_booking&quot;, ...)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    CG-&amp;gt;&amp;gt;MCP: tools/call(prepare_booking)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP-&amp;gt;&amp;gt;MCP: parse token sub for demo owner state&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP-&amp;gt;&amp;gt;P1: token exchange&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    P1--&amp;gt;&amp;gt;MCP: backend API token&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP-&amp;gt;&amp;gt;API: POST /booking-quotes&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    API--&amp;gt;&amp;gt;MCP: quote with nightlyRate + totalPrice&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP-&amp;gt;&amp;gt;PDP: PDP decision(prepare_booking with quote)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    PDP--&amp;gt;&amp;gt;MCP: PERMIT, optional USER-AZ-REQUIRED&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    MCP-&amp;gt;&amp;gt;API: POST /booking-intents&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    API--&amp;gt;&amp;gt;MCP: bookingIntent pending&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    alt USER-AZ-REQUIRED returned&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        MCP-&amp;gt;&amp;gt;P1: POST cibaAuthorization&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        P1--&amp;gt;&amp;gt;MCP: auth_req_id + expires_in + interval&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        MCP--&amp;gt;&amp;gt;CG: bookingApproval pending&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        CG-&amp;gt;&amp;gt;W: provide updated tool result&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        loop while pending&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            W-&amp;gt;&amp;gt;CG: window.openai.callTool(&quot;finalize_booking&quot;, ...)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            CG-&amp;gt;&amp;gt;MCP: tools/call(finalize_booking)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            MCP-&amp;gt;&amp;gt;PDP: PDP decision(finalize_booking)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            PDP--&amp;gt;&amp;gt;MCP: PERMIT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            MCP-&amp;gt;&amp;gt;MCP: parse token sub and verify ownerSub matches&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            MCP-&amp;gt;&amp;gt;P1: POST token (CIBA poll)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            P1--&amp;gt;&amp;gt;MCP: pending or final state&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            opt approved&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                MCP-&amp;gt;&amp;gt;P1: token exchange&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                P1--&amp;gt;&amp;gt;MCP: backend API token&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                MCP-&amp;gt;&amp;gt;API: POST /booking-intents/:transactionId/confirm&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                API--&amp;gt;&amp;gt;MCP: bookingIntent confirmed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            MCP--&amp;gt;&amp;gt;CG: bookingApproval update&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            CG-&amp;gt;&amp;gt;W: provide updated tool result&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    else no human-in-the-loop obligation&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        MCP-&amp;gt;&amp;gt;API: POST /booking-intents/:transactionId/confirm&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        API--&amp;gt;&amp;gt;MCP: bookingIntent confirmed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        MCP--&amp;gt;&amp;gt;CG: bookingApproval approved&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        CG-&amp;gt;&amp;gt;W: provide updated tool result&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Booking flow with CIBA-based approval&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;MyHotels&lt;/code&gt; is a compact but realistic example of how Ping Identity can secure a ChatGPT personal agent across different trust boundaries. In this demo, PingOne provides the main identity capabilities that make the flow work safely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;user authentication when the agent crosses into protected functionality&lt;/li&gt;
&lt;li&gt;centralized MCP authorization through PingOne Authorize&lt;/li&gt;
&lt;li&gt;audience and scope-specific token issuance for the MCP layer&lt;/li&gt;
&lt;li&gt;OAuth 2.0 Token Exchange so the backend API receives a token minted for its own protected resource&lt;/li&gt;
&lt;li&gt;actor-chain visibility that preserves how the request moved from ChatGPT to MCP to the backend&lt;/li&gt;
&lt;li&gt;CIBA-based approval for higher-risk actions such as booking&lt;/li&gt;
&lt;li&gt;policy-based thresholds that can allow, step up, or deny booking actions based on amount&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Taken together, those capabilities let the personal agent move from public access to authenticated access and then to explicit transaction approval without collapsing everything into one token or one client-side flow.&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Demo app: &lt;a href=&quot;https://github.com/carbonefederico/ai-myhotels-chatgpt-app&quot;&gt;ai-myhotels-chatgpt-app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OAuth 2.0 Token Exchange spec: &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8693&quot;&gt;RFC 8693&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CIBA spec: &lt;a href=&quot;https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html&quot;&gt;OpenID Client-Initiated Backchannel Authentication (CIBA) Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OpenAI Apps SDK: &lt;a href=&quot;https://developers.openai.com/apps-sdk&quot;&gt;OpenAI Apps SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.BpDpkEdj_2uRV7X.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Help us shape the future of Ping Identity&apos;s documentation</title><link>https://developer.pingidentity.com/blog/help-us-shape-the-future-of-ping-identity-documentation/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/help-us-shape-the-future-of-ping-identity-documentation/</guid><description>Take the 2026 Annual Documentation Feedback Survey and help Ping Identity prioritize the improvements that matter most to developers and technical implementers.</description><pubDate>Mon, 22 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you use Ping Identity documentation to build, configure, troubleshoot, or learn, we’d love your feedback. Our 2026 Annual Documentation Feedback Survey takes 5 – 10 minutes to complete and is designed to help us prioritize improvements that will make the biggest difference for you.&lt;/p&gt;
&lt;p&gt;This year’s survey asks about the full documentation experience: how easy it is to find content, whether information is easy to apply, how well the docs help you resolve issues, and whether they help you discover new ways to use the products.&lt;/p&gt;
&lt;p&gt;It also asks what kinds of content you want more of, including developer content such as code samples, example apps, and SDK or API docs. In addition, we’re asking how you use AI tools to find Ping Identity information and what you want those tools to do better for you.&lt;/p&gt;
&lt;p&gt;For developers and technical implementers, that matters. Good documentation should help you get started faster, find the right configuration details, connect information across pages, and turn examples into something you can actually use in your environment. Your feedback helps us see where we’re doing that well and where we still need to improve.&lt;/p&gt;
&lt;p&gt;If you’re willing to go deeper, the survey also includes an option to volunteer for a 30-minute follow-up interview or other research activities. That gives us a chance to understand the story behind the scores and comments, not just the numbers.&lt;/p&gt;
&lt;h2&gt;Take the survey&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://surveys.hotjar.com/97d3b479-91fd-4225-ae6c-6b0d2848d19a&quot;&gt;Take the Annual Documentation Feedback Survey&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks in advance for helping us improve the documentation experience for builders, admins, and everyone else who depends on Ping documentation to get work done.&lt;/p&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.CmlKSW6q_mnqbE.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Docs for Agents: making Ping Identity documentation work with AI</title><link>https://developer.pingidentity.com/blog/docs-for-agents/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/docs-for-agents/</guid><description>How we made our docs easy for AI agents to discover and consume.</description><pubDate>Fri, 19 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Developers increasingly build with AI coding agents, and one of the first things those agents do when working on an identity integration is try to pull in your docs. If your documentation isn’t structured for that, the agent either gets noisy HTML it has to parse, or it gives up and hallucinates the API surface instead.&lt;/p&gt;
&lt;p&gt;There are ways to make your docs more agent-friendly, and we recently implemented a number of these solutions for Ping Identity’s documentation. The result: our docs are now much easier for agents to discover and consume, which means better accuracy and a smoother experience for developers building with AI.&lt;/p&gt;
&lt;h2&gt;The problem: great docs, invisible to agents&lt;/h2&gt;
&lt;p&gt;Ping Identity has extensive technical documentation — thousands of pages across &lt;a href=&quot;https://docs.pingidentity.com&quot;&gt;docs.pingidentity.com&lt;/a&gt; and &lt;a href=&quot;https://developer.pingidentity.com&quot;&gt;developer.pingidentity.com&lt;/a&gt;. But “extensive” doesn’t automatically mean “agent-friendly.”&lt;/p&gt;
&lt;p&gt;Before this work, an AI agent trying to use our docs would hit a few walls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;HTML noise&lt;/strong&gt;: Documentation pages are built for browsers. Navigation chrome, scripts, and hundreds of lines of HTML overhead are irrelevant for an agent — and they’re expensive in terms of tokens. Stripping HTML to get to the actual content is possible, but imperfect, since the agent has to guess which parts are content and which parts are noise.&lt;/p&gt;
&lt;p&gt;Well-structured, semantic HTML does carry useful context, but in practice most HTML pages aren’t very well-structured or semantically rich — and even when they are, the signal-to-noise ratio rarely justifies the token cost.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No discovery mechanism&lt;/strong&gt;: Even if an agent knew our docs existed, there was no structured way for it to find the right page. It would have to either crawl the entire site or guess at URLs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No machine-readable product metadata&lt;/strong&gt;: Pages described products in natural language, but nothing told a search engine or agent in structured terms &lt;em&gt;which&lt;/em&gt; product a page was about.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On top of all this, AI-driven traffic already accounts for a significant percentage of site visits. We were already being crawled; we just weren’t giving those crawlers what they needed.&lt;/p&gt;
&lt;p&gt;To make matters more complex, there isn’t really a single “right” way to solve these problems. Best practices and specifications are still evolving, and it may be a while before an approach that works across most agents solidifies into a standard. Any solution we implemented had to be flexible enough to evolve as the ecosystem does, while still providing immediate value to developers building with AI today.&lt;/p&gt;
&lt;h2&gt;What we built&lt;/h2&gt;
&lt;p&gt;Because of the evolving landscape, we took a multipronged approach, implementing several complementary features to make our docs more agent-friendly. Our goal was to make it easy for agents to discover the docs they need, give them a clean, efficient way to fetch the content, and provide structured metadata to help them understand what they’re looking at, all without diminishing the experience for human readers.&lt;/p&gt;
&lt;h3&gt;Markdown alternates for every page&lt;/h3&gt;
&lt;p&gt;The most significant single change: every documentation page now has a Markdown alternate at the same URL, with &lt;code&gt;.html&lt;/code&gt; swapped for &lt;code&gt;.md&lt;/code&gt;, which lives parallel to the HTML version. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://docs.pingidentity.com/pingam/8/setup/am-admin-interface-tools.html&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://docs.pingidentity.com/pingam/8/setup/am-admin-interface-tools.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Markdown alternates strip out navigation, scripts, and all the browser-facing overhead, leaving just the content. Pulling Markdown instead of HTML can eliminate a significant portion of the data an agent would otherwise have to process as noise. That’s a dramatic drop in token cost and a meaningful improvement in accuracy. Taking a random sampling of pages across our docsets, the Markdown version is typically between 85% and 95% smaller than the HTML version, while still retaining the content an AI agent actually cares about.&lt;/p&gt;
&lt;p&gt;To improve discovery of the Markdown alternates, we also added a &lt;code&gt;&amp;lt;link rel=&quot;alternate&quot; type=&quot;text/markdown&quot;&amp;gt;&lt;/code&gt; element in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of every HTML page, pointing to the corresponding Markdown URL. This way, an agent that starts with the HTML page can easily find the Markdown version without having to guess the URL structure. To help humans discover it too, we also added a link to the Markdown version at the top of each page.&lt;/p&gt;
&lt;p&gt;We generate the markdown versions based on the published HTML, rather than just exposing our raw files. This is because we write our documentation using AsciiDoc. While most agents are perfectly happy to handle AsciiDoc, we do enough post processing during our build pipeline that it wasn’t feasible to just expose the raw AsciiDoc, and if we were going to have to process the files anyway, we may as well align with what has become an unofficial standard for AI-oriented docs. The Markdown generation happens at build time via an extension to our Antora build pipeline. Building the Markdown and the HTML from the same source content keeps them in sync — you can’t accidentally publish docs that have one without the other.&lt;/p&gt;
&lt;h3&gt;llms.txt indexes&lt;/h3&gt;
&lt;p&gt;Markdown alternates solve &lt;em&gt;fetching&lt;/em&gt; a known page. But how does an agent know which pages exist?&lt;/p&gt;
&lt;p&gt;The answer is &lt;code&gt;llms.txt&lt;/code&gt;, an &lt;a href=&quot;https://llmstxt.org&quot;&gt;emerging standard&lt;/a&gt; for giving AI models a curated, structured index of a site — similar in spirit to &lt;code&gt;robots.txt&lt;/code&gt;, but designed for context consumption rather than crawling control.&lt;/p&gt;
&lt;p&gt;We generate &lt;code&gt;llms.txt&lt;/code&gt; files as part of the same Markdown build pipeline, at two levels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-docset&lt;/strong&gt;: &lt;code&gt;https://docs.pingidentity.com/pingam/llms.txt&lt;/code&gt; lists every page in the PingAM docset with its title and Markdown URL. An agent working on a PingAM integration can load this file to get a full inventory of available content, then selectively fetch the pages relevant to its task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Site-wide&lt;/strong&gt;: &lt;code&gt;https://docs.pingidentity.com/llms.txt&lt;/code&gt; and &lt;code&gt;https://developer.pingidentity.com/llms.txt&lt;/code&gt; aggregate the per-docset files into a single index organized by product.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;JSON-LD structured data&lt;/h3&gt;
&lt;p&gt;In parallel with the Markdown and &lt;code&gt;llms.txt&lt;/code&gt; work, we added &lt;a href=&quot;https://json-ld.org&quot;&gt;JSON-LD&lt;/a&gt; structured data to every documentation page. JSON-LD is a machine-readable format for expressing things like “this page is a TechArticle about PingAM, published on this date, by Ping Identity.”&lt;/p&gt;
&lt;p&gt;Search engines and AI knowledge graphs already know how to consume this. Adding it to our pages helps both SEO and agent context — an agent parsing a docs page can read the JSON-LD block and immediately understand the product, product version, and content type rather than having to infer it from the natural language text. For a first pass, establishing the product/content-type relationship cleanly was the highest-value thing to ship, with room to iterate as needs evolve.&lt;/p&gt;
&lt;h3&gt;A “Docs for Agents” landing page&lt;/h3&gt;
&lt;p&gt;To tie all of this together for humans and agents alike, we published a &lt;a href=&quot;https://developer.pingidentity.com/build-with-ai/docs-for-agents.html&quot;&gt;Docs for Agents&lt;/a&gt; page explaining how everything fits together: where the Markdown alternates are, how the &lt;code&gt;llms.txt&lt;/code&gt; files are structured, and recommended patterns for loading focused context on a specific product vs. discovering what’s available across the full site.&lt;/p&gt;
&lt;p&gt;This isn’t necessarily a standard, but it’s a useful resource, and one that others in the industry have also opted to do (Cloudflare has a &lt;a href=&quot;https://developers.cloudflare.com/docs-for-agents/&quot;&gt;similar page&lt;/a&gt;, for instance). It’s a way to signal to developers that “yes, we know you’re using AI agents, and yes, we have features in place to support that.”&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;Accept: text/markdown&lt;/code&gt; content negotiation&lt;/h3&gt;
&lt;p&gt;Some agents send an &lt;code&gt;Accept: text/markdown&lt;/code&gt; header with their requests, expecting the server to return Markdown if it’s available. We’ve recently added support for this, including a fallback to the HTML version if the Markdown version isn’t available for some reason.&lt;/p&gt;
&lt;p&gt;This approach is gaining a lot of traction as a more elegant way to handle Markdown alternates, since it doesn’t require the agent to manage two separate URLs for each page. The agent can just request the normal URL and let content negotiation handle the rest. It’s still early days for this pattern, and not all agents support it yet, but we wanted to be ready for it as it becomes more common. You can learn more about this approach at &lt;a href=&quot;https://acceptmarkdown.com&quot;&gt;Accept: text/markdown&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Try it out&lt;/h2&gt;
&lt;p&gt;If you’re building an integration and want to give your AI coding agent focused context on a Ping product:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Point the agent at the product’s &lt;code&gt;llms.txt&lt;/code&gt;&lt;/strong&gt;: For example, &lt;code&gt;https://docs.pingidentity.com/pingam/llms.txt&lt;/code&gt; for PingAM. The agent can scan the titles and selectively fetch the pages most relevant to what you’re building.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fetch Markdown instead of HTML&lt;/strong&gt;: Swap &lt;code&gt;.html&lt;/code&gt; for &lt;code&gt;.md&lt;/code&gt; on any page URL, or extract the &lt;code&gt;&amp;lt;link rel=&quot;alternate&quot; type=&quot;text/markdown&quot;&amp;gt;&lt;/code&gt; URL from the page &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;. If your agent supports it, you can also just send an &lt;code&gt;Accept: text/markdown&lt;/code&gt; header and let content negotiation do the work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not sure which product docset you need?&lt;/strong&gt; Start at &lt;code&gt;https://docs.pingidentity.com/llms.txt&lt;/code&gt; or &lt;code&gt;https://developer.pingidentity.com/llms.txt&lt;/code&gt; for an index of everything available.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;What’s next&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Enriched page descriptions for &lt;code&gt;llms.txt&lt;/code&gt; entries&lt;/strong&gt;: Right now each entry in an &lt;code&gt;llms.txt&lt;/code&gt; file is just the page title and URL. The spec recommends a short description (≤150 characters) alongside each link. We’re updating all of our docs pages to include these descriptions, which should help make the &lt;code&gt;llms.txt&lt;/code&gt; indexes even more useful.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docs MCP server&lt;/strong&gt;: The longer-term vision is a Docs MCP server that exposes our documentation as callable tools rather than just static files. An agent using MCP would be able to search across products, retrieve specific sections, and get structured answers without having to manage the &lt;code&gt;llms.txt&lt;/code&gt;/Markdown workflow manually. This is in active development and should be launching soon.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Monitoring and iteration&lt;/strong&gt;: We’re actively monitoring how agents are using these features and gathering feedback from developers. The AI ecosystem is evolving rapidly, so we’re committed to iterating on our approach as best practices and standards emerge.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do you have thoughts or questions on this article? Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Specifications&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://acceptmarkdown.com&quot;&gt;Accept: text/markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://agentdocsspec.com&quot;&gt;Agent-Friendly Documentation Spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://json-ld.org&quot;&gt;JSON-LD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://llmstxt.org&quot;&gt;llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;“Docs for Agents” Examples&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.pingidentity.com/build-with-ai/docs-for-agents.html&quot;&gt;Ping Identity’s Docs for Agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.cloudflare.com/docs-for-agents/&quot;&gt;Cloudflare’s Docs for Agents&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/ping_agents.CrloqI8T_2axEvt.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Securing the PingOne DaVinci Widget with the Backend-for-Frontend (BFF) Pattern</title><link>https://developer.pingidentity.com/blog/securing-the-pingone-davinci-widget-with-the-backend-for-frontend-bff-pattern/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/securing-the-pingone-davinci-widget-with-the-backend-for-frontend-bff-pattern/</guid><description>A walkthrough of how to embed the DaVinci widget in an SPA while keeping OAuth tokens and DaVinci API credentials safely on a Node.js BFF backend.</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Web application architecture has gone through several major evolutions over the last 30 years - and I’m old enough to have lived through most of them. Thankfully, we’ve largely moved on from the era of server-rendered UI frameworks like Java Servlets, PHP, and ASP applications that rebuilt an entire page on every interaction. In their place, single page applications (SPAs) have become the dominant model for modern web experiences.&lt;/p&gt;
&lt;p&gt;SPAs dramatically improve responsiveness and usability by moving much of the application control logic into the browser, where JavaScript can interact directly with backend APIs without requiring constant full-page refreshes. From a user experience perspective, the benefits are undeniable.&lt;/p&gt;
&lt;p&gt;For identity architects and security practitioners, however, this shift introduced a new set of challenges. Traditional authentication and session-management patterns were designed around trusted server-side applications. As applications moved toward increasingly “pure” SPA architectures, those patterns became harder to apply safely and consistently.&lt;/p&gt;
&lt;p&gt;In my opinion, the challenge of building a truly secure browser-only SPA remains largely unsolved. While modern OAuth and browser security standards have improved significantly, exposing sensitive OAuth tokens directly to browser code still creates risks that many teams underestimate. That’s why I continue to believe that a secure backend following the Backend-for-Frontend (BFF) pattern is a critical component of any security-sensitive SPA architecture.&lt;/p&gt;
&lt;p&gt;In this article, we’ll walk through an approach for securing the PingOne DaVinci embedded widget using a BFF architecture that prevents sensitive OAuth tokens from ever being exposed to the browser client. The core idea is simple: keep the widget in the browser for the user experience, but keep the trust decisions, token handling, and application session establishment in the backend.&lt;/p&gt;
&lt;h2&gt;What is a widget anyway (and why would we use one?)&lt;/h2&gt;
&lt;p&gt;As a modern identity orchestration platform, DaVinci provides multiple integration models that allow applications to consume orchestration flows in the way that best matches their architecture and UX goals.&lt;/p&gt;
&lt;p&gt;For modern web applications, embedded authentication experiences have become increasingly popular. Rather than redirecting users to a hosted login page, authentication journeys are rendered directly within the application itself, creating a more seamless user experience. While there are certainly security tradeoffs worth discussing, the approach is now common enough that identity platforms need to support it well.&lt;/p&gt;
&lt;p&gt;DaVinci offers two primary approaches for embedded authentication experiences:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Headless integration using APIs and SDKs. Applications can use DaVinci APIs and SDKs to implement the entire authentication experience directly in client-side code. This gives development teams maximum control over UI and UX behavior, but also requires them to own and maintain the interaction logic themselves.&lt;/li&gt;
&lt;li&gt;The embedded DaVinci widget. The pre-built DaVinci widget provides a faster and more flexible way to embed authentication flows directly into a web application while allowing DaVinci to handle the orchestration and flow-rendering logic. The result is a seamless embedded experience with significantly less implementation effort.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In this article, we’ll focus specifically on securing the embedded DaVinci widget integration itself rather than exploring every possible DaVinci deployment model. In particular, we’ll concentrate on two key security objectives. First, we’ll show how the widget can rely on a trusted backend service to securely initiate DaVinci flows without exposing sensitive DaVinci API credentials to browser-based JavaScript. Second, we’ll show how to complete the authentication process without returning OAuth tokens, DaVinci session tokens, or other sensitive handoff artifacts through the browser front channel.&lt;/p&gt;
&lt;p&gt;To demonstrate the approach, we’ll use a deliberately simple reference architecture consisting of two components. The frontend application is implemented as a lightweight HTML and JavaScript SPA that runs entirely in the browser and hosts the embedded DaVinci widget. Supporting this is a minimal backend service implemented in Node.js using the Express framework. By keeping the architecture intentionally straightforward, the example focuses on the core security concepts without introducing unnecessary framework complexity.&lt;/p&gt;
&lt;h2&gt;Why this pattern is worth the extra work&lt;/h2&gt;
&lt;p&gt;With an embedded authentication widget, there is an unavoidable architectural tension. The browser needs to host the user experience, because that is the whole point of embedding the widget. But the browser is not where we want to handle API credentials, OAuth tokens, refresh tokens, or durable authentication state.&lt;/p&gt;
&lt;p&gt;The pattern in this article draws a clean boundary. The widget still renders in the browser, but the BFF creates the authentication transaction, DaVinci binds the completed flow back to that transaction, and the tokens are delivered directly from DaVinci to the backend over a server-to-server callback. The browser is responsible for the interactive experience and for asking the BFF to finalize the local application session. It is not responsible for transporting token material.&lt;/p&gt;
&lt;p&gt;That is why this pattern works well. It preserves the embedded login experience, keeps OAuth tokens out of frontend JavaScript, avoids exposing DaVinci API credentials, gives the BFF a server-side transaction to validate, and lets the BFF regenerate the application session only after it has verified signed token claims that match the transaction it originally created.&lt;/p&gt;
&lt;p&gt;Full code for the sample implementation described below is available in &lt;a href=&quot;https://github.com/robotto-ping/acme-davinci-bff-public&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The code in this article is provided as a sample for educational purposes and is not intended for direct production use without review and adaptation for your specific environment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Step 1: Securely obtaining a DaVinci SDK token&lt;/h2&gt;
&lt;p&gt;The first challenge when embedding the DaVinci widget into a browser-based SPA is securely obtaining the SDK token required to initiate a flow. Normally, generating this token requires a DaVinci API key - a highly sensitive credential that should never be exposed to browser JavaScript or embedded into frontend source code.&lt;/p&gt;
&lt;p&gt;To avoid this, the browser never communicates directly with the DaVinci &lt;code&gt;/sdktoken&lt;/code&gt; endpoint. Instead, the frontend calls a trusted backend endpoint (&lt;code&gt;/dvtoken&lt;/code&gt;) implemented in our Express-based BFF service. The backend securely stores the DaVinci API key using environment variables and uses it to request a short-lived SDK token from the DaVinci orchestration API.&lt;/p&gt;
&lt;p&gt;In this pattern, &lt;code&gt;/dvtoken&lt;/code&gt; does one more important thing. Before requesting the SDK token, the BFF creates a short-lived authentication transaction. This transaction includes a &lt;code&gt;transactionID&lt;/code&gt;, a cryptographically strong nonce, and two time windows: one for starting the DaVinci flow and one for completing the overall authentication transaction.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; createAuthTransaction&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; issuedAt&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    transactionID: crypto.&lt;/span&gt;&lt;span&gt;randomUUID&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    nonce: crypto.&lt;/span&gt;&lt;span&gt;randomBytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;32&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;base64url&apos;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    issuedAt,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    startBy: issuedAt &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; AUTH_FLOW_START_TTL_MS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    completeBy: issuedAt &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; AUTH_TRANSACTION_TTL_MS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    status: &lt;/span&gt;&lt;span&gt;&apos;pending&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These two time windows are intentionally different. Starting the widget should happen quickly, so &lt;code&gt;startBy&lt;/code&gt; can be short. Completing the journey could take longer, especially if the flow includes MFA, identity verification, or any other user interaction that takes time, so &lt;code&gt;completeBy&lt;/code&gt; should be longer.&lt;/p&gt;
&lt;p&gt;The BFF stores this transaction server-side and also stores a copy in the pre-auth Express session associated with the browser. It then passes selected values into the DaVinci flow as SDK token parameters:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  policyId: &lt;/span&gt;&lt;span&gt;WIDGET_POLICY_ID&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  parameters: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    transactionID: authTransaction.transactionID,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    nonce: authTransaction.nonce,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    expiresAt: authTransaction.startBy&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The browser doesn’t need to know these values. They’re generated by the BFF and delivered directly to DaVinci as flow input parameters. The browser receives only the widget SDK token and the minimal configuration needed to render the widget.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	token: data.access_token,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	companyId: &lt;/span&gt;&lt;span&gt;COMPANY_ID&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	policyId: &lt;/span&gt;&lt;span&gt;WIDGET_POLICY_ID&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	apiRoot: &lt;/span&gt;&lt;span&gt;API_ROOT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 2: Invoking the login flow with the widget&lt;/h2&gt;
&lt;p&gt;With the SDK token in hand, the frontend can initialize the DaVinci widget and invoke the login flow. The widget is configured to use &lt;code&gt;runFlow&lt;/code&gt;, along with the &lt;code&gt;apiRoot&lt;/code&gt;, &lt;code&gt;companyId&lt;/code&gt;, &lt;code&gt;policyId&lt;/code&gt;, and short-lived SDK token returned by the BFF. At this point, the browser has everything it needs to render and execute the embedded DaVinci experience, but still doesn’t have access to any long-lived credentials, OAuth tokens, or BFF transaction secrets.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; tokenRes&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/dvtoken&apos;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	headers: { &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;token&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;companyId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;policyId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;apiRoot&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; tokenRes.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; props&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	config: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	method: &lt;/span&gt;&lt;span&gt;&apos;runFlow&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	apiRoot: apiRoot,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	accessToken: token,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	companyId: companyId,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	policyId: policyId&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	useModal: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	successCallback&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	const&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; finalizeLogin&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	showAuthenticatedState&lt;/span&gt;&lt;span&gt;(result.user);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;davinci.&lt;/span&gt;&lt;span&gt;skRenderScreen&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UI&lt;/span&gt;&lt;span&gt;.widget, props);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important nuance is in the DaVinci flow design. Here’s an example of a flow, with details below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of nodes connected in the DaVinci flow canvas that show the full authentication orchestration process&quot; loading=&quot;lazy&quot; width=&quot;2257&quot; height=&quot;354&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/davinci-flow-overview.C51zC7Do_12M4Dj.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/davinci-flow-overview.C51zC7Do_16JYMi.webp 640w, https://developer.pingidentity.com/blog/_astro/davinci-flow-overview.C51zC7Do_ZuayWx.webp 750w, https://developer.pingidentity.com/blog/_astro/davinci-flow-overview.C51zC7Do_Z1iDg5Y.webp 828w, https://developer.pingidentity.com/blog/_astro/davinci-flow-overview.C51zC7Do_Z7q4el.webp 1080w, https://developer.pingidentity.com/blog/_astro/davinci-flow-overview.C51zC7Do_ZBLcBa.webp 1280w, https://developer.pingidentity.com/blog/_astro/davinci-flow-overview.C51zC7Do_1RSpRw.webp 1668w, https://developer.pingidentity.com/blog/_astro/davinci-flow-overview.C51zC7Do_ZMbfPG.webp 2048w, https://developer.pingidentity.com/blog/_astro/davinci-flow-overview.C51zC7Do_12M4Dj.webp 2257w&quot; /&gt;&amp;gt;
&lt;em&gt;DaVinci flow overview showing the full authentication orchestration&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The flow should validate that the BFF-supplied transaction context is present and fresh before continuing with authentication. At minimum, the flow should check that &lt;code&gt;transactionID&lt;/code&gt;, &lt;code&gt;nonce&lt;/code&gt;, and &lt;code&gt;expiresAt&lt;/code&gt; are present, that &lt;code&gt;expiresAt&lt;/code&gt; is numeric, and that the current time is not later than &lt;code&gt;expiresAt&lt;/code&gt;. Our flow input schema validation takes care of the first three and we use a simple A&amp;lt;B function node for the fourth.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the DaVinci console showing a filled input schema paramater form. &amp;quot;nonce&amp;quot; is set as the parameter name, the data type is &amp;quot;String&amp;quot; and &amp;quot;Required&amp;quot; is checked.&quot; loading=&quot;lazy&quot; width=&quot;1374&quot; height=&quot;1012&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-input-validation-1.BA0CW3SQ_Z2qoVzd.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-input-validation-1.BA0CW3SQ_Z1Ae13b.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-1.BA0CW3SQ_d2Oq7.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-1.BA0CW3SQ_ZwlWCK.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-1.BA0CW3SQ_134dQT.webp 1080w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-1.BA0CW3SQ_21rg38.webp 1280w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-1.BA0CW3SQ_Z2qoVzd.webp 1374w&quot; /&gt;&amp;gt;
&lt;em&gt;Flow input validation step 1 - checking transactionID, nonce, and expiresAt fields are present&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the DaVinci console showing a filled input schema parameter form. &amp;quot;transactionID&amp;quot; is the parameter name, the data type is &amp;quot;String&amp;quot; and &amp;quot;Required&amp;quot; is checked.&quot; loading=&quot;lazy&quot; width=&quot;1344&quot; height=&quot;942&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-input-validation-2.Ch1Ywi28_Znz1F1.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-input-validation-2.Ch1Ywi28_mNaeq.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-2.Ch1Ywi28_Z1URxai.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-2.Ch1Ywi28_Zx8Xgn.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-2.Ch1Ywi28_e25fW.webp 1080w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-2.Ch1Ywi28_2w2WWh.webp 1280w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-2.Ch1Ywi28_Znz1F1.webp 1344w&quot; /&gt;&amp;gt;
&lt;em&gt;Flow input validation step 2 - schema validation configuration&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the DaVinci console showing a filled input schema paramater form. &amp;quot;expiresAt&amp;quot; is set as the parameter name, the data type is &amp;quot;Number&amp;quot; and &amp;quot;Required&amp;quot; is checked.&quot; loading=&quot;lazy&quot; width=&quot;1338&quot; height=&quot;904&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-input-validation-3.B18FJBeH_Z1DMLxX.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-input-validation-3.B18FJBeH_1YmAGT.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-3.B18FJBeH_Z1GESDB.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-3.B18FJBeH_Z1Gi6Gt.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-3.B18FJBeH_ZOvKTC.webp 1080w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-3.B18FJBeH_2wo6nm.webp 1280w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-3.B18FJBeH_Z1DMLxX.webp 1338w&quot; /&gt;&amp;gt;
&lt;em&gt;Flow input validation step 3 - additional schema validation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the DaVinci console showing a filled Functions connector node form, for the &amp;quot;A &amp;lt; B&amp;quot; trigger. &amp;quot;eventTimestamp&amp;quot; is set as &amp;quot;Value A&amp;quot;, &amp;quot;expiresAt&amp;quot; is set as &amp;quot;Value B&amp;quot;.&quot; loading=&quot;lazy&quot; width=&quot;1334&quot; height=&quot;1102&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-input-validation-4.DQL5-dBq_1UvXJI.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-input-validation-4.DQL5-dBq_ZpSrtE.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-4.DQL5-dBq_ZJpAcF.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-4.DQL5-dBq_12FzHN.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-4.DQL5-dBq_Z1pxFEv.webp 1080w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-4.DQL5-dBq_Z2e1R2J.webp 1280w, https://developer.pingidentity.com/blog/_astro/flow-input-validation-4.DQL5-dBq_1UvXJI.webp 1334w&quot; /&gt;&amp;gt;
&lt;em&gt;Flow input validation step 4 - A less than B expiry check&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This validation doesn’t prove that the transaction is valid by itself. The BFF remains the source of truth because it created the transaction. But it gives the DaVinci flow a clean way to reject stale or malformed starts before asking the user to authenticate.&lt;/p&gt;
&lt;h2&gt;Step 3: Generating tokens without returning them to the browser&lt;/h2&gt;
&lt;p&gt;After the user completes the embedded journey, DaVinci needs to generate the OAuth tokens. The critical design rule is that those tokens must not be returned to the widget success callback. Instead, DaVinci should deliver them directly to the BFF using the HTTP Connector’s “Make REST API Call” capability. The key flow design choice here is to ensure that the “PingOne Authentication Return Success Response (Widget Flows)” node is not the final one in the flow.&lt;/p&gt;
&lt;p&gt;To bind the generated token response to the BFF transaction, the ID token should include the BFF-generated nonce and transaction identifier. In the sample implementation, the custom transaction claim is &lt;code&gt;bff_transaction_id&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These claims need to be in the ID token. The BFF verifies the signed ID token and uses the claims in that token as proof that the token response is bound to the transaction it originally created.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the DaVinci console showing a filled PingOne Authentication connector node form, for the &amp;quot;Return Success Response (Widget Flows)&amp;quot; trigger. For the &amp;quot;ID Token Custom Claims&amp;quot; section, the &amp;quot;Claim Name&amp;quot; being &amp;quot;nonce&amp;quot; has &amp;quot;nonce&amp;quot; variable assigned, the &amp;quot;Claim Name&amp;quot; being &amp;quot;bff_transaction_id&amp;quot; has the &amp;quot;transactionID&amp;quot; variable assigned.&quot; loading=&quot;lazy&quot; width=&quot;1346&quot; height=&quot;1106&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/id-token-custom-claims.BjMYdun0_ZWTrh3.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/id-token-custom-claims.BjMYdun0_1lUETk.webp 640w, https://developer.pingidentity.com/blog/_astro/id-token-custom-claims.BjMYdun0_qtEHd.webp 750w, https://developer.pingidentity.com/blog/_astro/id-token-custom-claims.BjMYdun0_ZWiRsH.webp 828w, https://developer.pingidentity.com/blog/_astro/id-token-custom-claims.BjMYdun0_Z1up5Yr.webp 1080w, https://developer.pingidentity.com/blog/_astro/id-token-custom-claims.BjMYdun0_1wjIzI.webp 1280w, https://developer.pingidentity.com/blog/_astro/id-token-custom-claims.BjMYdun0_ZWTrh3.webp 1346w&quot; /&gt;&amp;gt;
&lt;em&gt;ID token custom claims configuration showing bff_transaction_id and nonce fields&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Step 4: Delivering tokens to the BFF server-to-server&lt;/h2&gt;
&lt;p&gt;After DaVinci generates the token response, it calls the BFF directly. This is the key architectural boundary in the design. The browser is never responsible for carrying an authentication artifact from DaVinci to the backend.&lt;/p&gt;
&lt;p&gt;The DaVinci HTTP connector calls &lt;code&gt;/auth/davinci/complete&lt;/code&gt; with a shared callback secret and a JSON body containing the transaction binding values and token response:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;POST /auth/davinci/complete&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Content-Type: application/json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;X-DaVinci-Callback-Secret: &amp;lt;shared callback secret&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;transactionID&quot;: &quot;bff-generated-transaction-id&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;nonce&quot;: &quot;bff-generated-nonce&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;completedAt&quot;: 1717000012345,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;interactionId&quot;: &quot;optional-davinci-interaction-id&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;tokens&quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;access_token&quot;: &quot;eyJ...&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;refresh_token&quot;: &quot;optional-refresh-token&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;id_token&quot;: &quot;eyJ...&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;token_type&quot;: &quot;Bearer&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;expires_in&quot;: 3600&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the DaVinci console showing a partial Http connector node form, for the &amp;quot;Make REST API Call&amp;quot; trigger. The &amp;quot;URL&amp;quot; value is set to &amp;quot;https://acme-davinci-project.onrender.com/auth/davinci/complete&amp;quot; and the &amp;quot;HTTP Method&amp;quot; is set to &amp;quot;POST&amp;quot;.&quot; loading=&quot;lazy&quot; width=&quot;1334&quot; height=&quot;942&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/http-connector-callback-1.DgPtVIiH_LEt1o.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/http-connector-callback-1.DgPtVIiH_230iba.webp 640w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-1.DgPtVIiH_ZKBMQK.webp 750w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-1.DgPtVIiH_1N8FXJ.webp 828w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-1.DgPtVIiH_Z1e4zVf.webp 1080w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-1.DgPtVIiH_Z1gRN3J.webp 1280w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-1.DgPtVIiH_LEt1o.webp 1334w&quot; /&gt;&amp;gt;
&lt;em&gt;HTTP connector callback configuration step 1 - Make REST API Call setup&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the DaVinci console showing a partial Http connector node form, for the same &amp;quot;Make REST API Call&amp;quot; trigger. The &amp;quot;X-DaVinci-Callback-Secret&amp;quot; header is set to the &amp;quot;callback_secret&amp;quot; variable.&quot; loading=&quot;lazy&quot; width=&quot;1304&quot; height=&quot;554&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/http-connector-callback-2.BGK3VaEj_2diQpG.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/http-connector-callback-2.BGK3VaEj_1fP2vp.webp 640w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-2.BGK3VaEj_ZEVwc.webp 750w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-2.BGK3VaEj_Ze8Kq.webp 828w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-2.BGK3VaEj_DAOaa.webp 1080w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-2.BGK3VaEj_1COfgQ.webp 1280w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-2.BGK3VaEj_2diQpG.webp 1304w&quot; /&gt;&amp;gt;
&lt;em&gt;HTTP connector callback configuration step 2 - request body mapping&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the DaVinci console showing a partial Http connector node form, for the same &amp;quot;Make REST API Call&amp;quot; trigger. The &amp;quot;raw&amp;quot; result shows the variable mappings for the token response example provided above.&quot; loading=&quot;lazy&quot; width=&quot;1320&quot; height=&quot;532&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/http-connector-callback-3.alN7dtJ__6055X.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/http-connector-callback-3.alN7dtJ__Zn6wO1.webp 640w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-3.alN7dtJ__Z1oFbq9.webp 750w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-3.alN7dtJ__2fKXSd.webp 828w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-3.alN7dtJ__rijzh.webp 1080w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-3.alN7dtJ__LqLcq.webp 1280w, https://developer.pingidentity.com/blog/_astro/http-connector-callback-3.alN7dtJ__6055X.webp 1320w&quot; /&gt;&amp;gt;
&lt;em&gt;HTTP connector callback configuration step 3 - full connector setup&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The BFF route is intentionally registered before the browser-origin middleware, because this request is not coming from the browser and will not have the same Origin and Fetch Metadata headers. Instead, the route is protected using the callback secret, token signature validation, and transaction binding checks.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;app.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&apos;/auth/davinci/complete&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	callbackRateLimit,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	express.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ limit: &lt;/span&gt;&lt;span&gt;&apos;20kb&apos;&lt;/span&gt;&lt;span&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	requireDavinciCallbackSecret,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;transactionID&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;nonce&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; req.body &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	const&lt;/span&gt;&lt;span&gt; transaction&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; authTransactions.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(transactionID);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;transaction &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; transaction.status &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &apos;pending&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        	return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;409&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ error: &lt;/span&gt;&lt;span&gt;&apos;Unknown or invalid transaction&apos;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;safeEqual&lt;/span&gt;&lt;span&gt;(nonce, transaction.nonce)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        	return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;403&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ error: &lt;/span&gt;&lt;span&gt;&apos;Invalid transaction binding&apos;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	const&lt;/span&gt;&lt;span&gt; claims&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; verifyIdToken&lt;/span&gt;&lt;span&gt;(tokens.id_token);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	const&lt;/span&gt;&lt;span&gt; tokenTransactionID&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getTransactionIDFromClaims&lt;/span&gt;&lt;span&gt;(claims);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;safeEqual&lt;/span&gt;&lt;span&gt;(tokenTransactionID, transactionID)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        	return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;403&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ error: &lt;/span&gt;&lt;span&gt;&apos;Invalid token transaction binding&apos;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;safeEqual&lt;/span&gt;&lt;span&gt;(claims.nonce, transaction.nonce)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        	return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;403&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ error: &lt;/span&gt;&lt;span&gt;&apos;Invalid token nonce binding&apos;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	transaction.status &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;tokens_delivered&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	transaction.idTokenClaims &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; claims;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	transaction.tokens &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; tokens;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	return&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ received: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above is intentionally abbreviated, but the validation order is important. The BFF first finds the pending transaction by &lt;code&gt;transactionID&lt;/code&gt;, then checks the &lt;code&gt;nonce&lt;/code&gt; supplied in the body, then verifies the signed ID token and checks that the signed claims contain the same &lt;code&gt;transactionID&lt;/code&gt; and &lt;code&gt;nonce&lt;/code&gt;. This gives us both a server-to-server delivery channel and a signed token-level binding to the transaction.&lt;/p&gt;
&lt;h2&gt;Step 5: Finalizing the BFF application session&lt;/h2&gt;
&lt;p&gt;The server-to-server callback cannot set the browser’s application session cookie because the browser is not involved in that request. That final step still needs to happen from the browser, but the browser doesn’t need to send any sensitive token material.&lt;/p&gt;
&lt;p&gt;When the DaVinci widget completes successfully, the frontend calls &lt;code&gt;/auth/finalize&lt;/code&gt; with an empty JSON body:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; finalizeLogin&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; finalizeRes&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/auth/finalize&apos;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	headers: { &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;finalizeRes.ok) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`Finalize failed with HTTP ${&lt;/span&gt;&lt;span&gt;finalizeRes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; finalizeRes.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This route uses the pre-auth Express session cookie to find the pending transaction that was created when &lt;code&gt;/dvtoken&lt;/code&gt; was called. It then checks that DaVinci has already delivered tokens for that transaction. If the token delivery is complete and all bindings match, the BFF regenerates the Express session and promotes it into an authenticated application session.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; regenerateSession&lt;/span&gt;&lt;span&gt;(req);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;req.session.authenticated &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;req.session.user &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; user;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;req.session.subject &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; transaction.subject;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;req.session.access_token &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; tokens.access_token;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;req.session.refresh_token &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; tokens.refresh_token;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;req.session.id_token &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; tokens.id_token;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;req.session.id_token_claims &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; idTokenClaims;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; saveSession&lt;/span&gt;&lt;span&gt;(req);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This session regeneration step is important. The pre-auth session exists only to bind the browser to the pending authentication transaction. After authentication is complete, the BFF discards that pre-auth session identifier and creates a fresh authenticated session. This reduces session fixation risk and gives the application a clean transition from unauthenticated to authenticated state.&lt;/p&gt;
&lt;p&gt;The browser receives only a normal application response and a secure application session cookie. It never sees the OAuth tokens, the DaVinci API key, or a DaVinci sessionToken.&lt;/p&gt;
&lt;h2&gt;Step 6: Hardening the browser-facing routes&lt;/h2&gt;
&lt;p&gt;The BFF also includes browser-origin checks for the routes that are intended to be called by the frontend. CORS is useful here, but it is important not to overstate what CORS does. CORS is enforced by browsers; it doesn’t authenticate arbitrary non-browser HTTP clients.&lt;/p&gt;
&lt;p&gt;For that reason, the BFF uses origin checks as a browser-side protection layer, not as the only security control. The server-to-server DaVinci callback uses different protection because it is not a browser request.&lt;/p&gt;
&lt;p&gt;For the browser session cookie, the implementation uses an application-scoped cookie with the &lt;code&gt;HttpOnly&lt;/code&gt;, &lt;code&gt;Secure&lt;/code&gt;, &lt;code&gt;SameSite=Strict&lt;/code&gt; attributes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; sessionCookieOptions&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	httpOnly: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	secure: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	sameSite: &lt;/span&gt;&lt;span&gt;&apos;strict&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	path: &lt;/span&gt;&lt;span&gt;&apos;/&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	maxAge: &lt;/span&gt;&lt;span&gt;SESSION_TTL_MS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The cookie is named &lt;code&gt;__Host-acme_session&lt;/code&gt;. The &lt;code&gt;__Host-&lt;/code&gt; prefix gives the browser additional constraints: the cookie must be &lt;code&gt;Secure&lt;/code&gt;, must use &lt;code&gt;Path=/&lt;/code&gt;, and must not include a &lt;code&gt;Domain&lt;/code&gt; attribute. This helps keep the session cookie tightly scoped to the application host.&lt;/p&gt;
&lt;p&gt;This doesn’t eliminate bearer cookies. A BFF application still needs a browser cookie to represent the application session. The point is that the cookie is now an app-local session handle controlled by the BFF, not an OAuth token, refresh token, DaVinci session token, or DaVinci token reference.&lt;/p&gt;
&lt;h2&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;The full end-to-end flow now looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The browser calls &lt;code&gt;/dvtoken&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The BFF creates &lt;code&gt;transactionID&lt;/code&gt;, &lt;code&gt;nonce&lt;/code&gt;, &lt;code&gt;startBy&lt;/code&gt;, and &lt;code&gt;completeBy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The BFF requests a DaVinci SDK token and passes the transaction context into the flow.&lt;/li&gt;
&lt;li&gt;The browser renders the embedded widget using the returned SDK token.&lt;/li&gt;
&lt;li&gt;DaVinci validates the transaction context and authenticates the user.&lt;/li&gt;
&lt;li&gt;DaVinci generates OAuth tokens and includes &lt;code&gt;nonce&lt;/code&gt; and &lt;code&gt;bff_transaction_id&lt;/code&gt; in the ID token.&lt;/li&gt;
&lt;li&gt;DaVinci calls &lt;code&gt;/auth/davinci/complete&lt;/code&gt; server-to-server and delivers the tokens to the BFF.&lt;/li&gt;
&lt;li&gt;The BFF validates the callback secret, pending transaction, body nonce, ID token signature, ID token audience, ID token issuer, ID token nonce, and ID token transaction claim.&lt;/li&gt;
&lt;li&gt;The widget success callback fires in the browser.&lt;/li&gt;
&lt;li&gt;The browser calls &lt;code&gt;/auth/finalize&lt;/code&gt; with no token material.&lt;/li&gt;
&lt;li&gt;The BFF validates the browser session binding and promotes the transaction into an authenticated application session.&lt;/li&gt;
&lt;li&gt;The browser receives only the application session cookie.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&quot;A flow diagram showing the interactions between the Browser SPA, the Express BFF, PingOne DaVinci and PingOne OIDC/JWKS entities.&quot; loading=&quot;lazy&quot; width=&quot;1524&quot; height=&quot;1256&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/end-to-end-flow-diagram.CqM0poOJ_Z1iBXNB.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/end-to-end-flow-diagram.CqM0poOJ_Ynzzq.webp 640w, https://developer.pingidentity.com/blog/_astro/end-to-end-flow-diagram.CqM0poOJ_Z1YsQif.webp 750w, https://developer.pingidentity.com/blog/_astro/end-to-end-flow-diagram.CqM0poOJ_2rWBLS.webp 828w, https://developer.pingidentity.com/blog/_astro/end-to-end-flow-diagram.CqM0poOJ_1JvcQy.webp 1080w, https://developer.pingidentity.com/blog/_astro/end-to-end-flow-diagram.CqM0poOJ_V21tk.webp 1280w, https://developer.pingidentity.com/blog/_astro/end-to-end-flow-diagram.CqM0poOJ_Z1iBXNB.webp 1524w&quot; /&gt;&amp;gt;
&lt;em&gt;End-to-end flow diagram showing all 12 steps of the BFF authentication pattern&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While there are a few moving parts, the responsibility boundaries are straightforward. The browser is responsible for rendering the embedded DaVinci widget and collecting the user’s interaction. DaVinci is responsible for executing the identity journey and generating tokens. The BFF is responsible for protecting credentials, validating token delivery, binding the transaction, and establishing the application session.&lt;/p&gt;
&lt;h2&gt;A few practical caveats&lt;/h2&gt;
&lt;p&gt;The sample implementation deliberately keeps the architecture simple, but there are a few production considerations worth calling out.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The sample stores pending authentication transactions in an in-memory Map. That is fine for a single demo service, but production deployments should use Redis, a database, or another shared server-side store.&lt;/li&gt;
&lt;li&gt;The sample uses the default express-session store. That is not suitable for production scale. A production BFF should use a real session store.&lt;/li&gt;
&lt;li&gt;The DaVinci callback currently uses a shared static secret. A stronger version would use an HMAC signature over the request body and timestamp, mTLS, or another stronger server-to-server authentication mechanism where available.&lt;/li&gt;
&lt;li&gt;Origin and CORS checks are useful for browser-based protection, but they do not authenticate non-browser clients. The critical protection for token delivery is the combination of callback authentication, signed ID token validation, and BFF transaction binding.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Over to you&lt;/h2&gt;
&lt;p&gt;It would be great to hear how others are approaching this pattern in their own environments.&lt;/p&gt;
&lt;p&gt;Are you already using a BFF-style architecture to protect OAuth tokens in SPA applications, or are your teams still comfortable managing tokens directly in the browser? When embedding authentication journeys with tools like the DaVinci widget, where do you draw the line between user experience convenience and token-handling risk? And have you found other patterns that preserve the seamless embedded login experience without exposing long-lived credentials or OAuth tokens to frontend code?&lt;/p&gt;
&lt;p&gt;Drop a comment and join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt; with your thoughts, questions, or your own lessons learned from building secure modern web applications in the identity trenches.&lt;/p&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.DQK7w9P9_64kfj.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Understanding the Org Model</title><link>https://developer.pingidentity.com/blog/understanding-the-org-model-aic/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/understanding-the-org-model-aic/</guid><description>Learn how PingOne Advanced Identity Cloud and PingIDM&apos;s Organizations enable out-of-the-box extensible, inherited relationships between users and associated groups of users</description><pubDate>Thu, 28 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This post was originally published on &lt;a href=&quot;https://gwizkid.com/posts/understanding-the-org-model-aic/&quot;&gt;Gwiz Kid&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;PingOne Advanced Identity Cloud (AIC) and PingIDM (Ping’s self-managed Identity Management) provide a powerful model called Organizations which enables out-of-the-box extensible, inherited relationships between users and associated groups of users. In short, Organizations give you the ability to match your IAM strategy to your business strategy, and not the other way around - they are a means to unify, simplify, and standardize your process across your internal, external, and partner identities.&lt;/p&gt;
&lt;p&gt;Considering its flexibility and scalability, starting with an empty tenant and the base Organization model can feel a bit daunting.&lt;/p&gt;
&lt;p&gt;This Guide intends to teach you the key information you need to know when interacting with AIC/PingIDM’s Organization model. It is split into the following sections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#object-modeling-definitions&quot;&gt;Object Modeling Definitions&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#whats-the-deal-with-rdvps&quot;&gt;What’s the Deal with RDVPs?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#what-is-the-organization-model&quot;&gt;What is the Organization Model?&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#a-quick-analogy&quot;&gt;A Quick Analogy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#utilizing-the-organization-model&quot;&gt;Utilizing the Organization Model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#referencing-the-organization-model-in-the-platform&quot;&gt;Referencing the Organization Model in the Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#extending-the-organization-model&quot;&gt;Extending the Organization Model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the end of this Guide you’ll have a medium-level understanding of Organizations and how they operate generically within AIC and PingIDM.&lt;/p&gt;
&lt;p&gt;This Guide expects a beginner level of familiarity with AIC or PingIDM. Certain topics covered here will be specific to the AIC platform: when this happens, we’ll call it out. If you do have a tenant or deployment available, feel free to follow along however it is not necessary.&lt;/p&gt;
&lt;h1&gt;Object Modeling Definitions&lt;/h1&gt;
&lt;p&gt;Before we define Organizations, we need to understand the underlying infrastructure that surrounds it. Let’s start broadly and then narrow down as we go.&lt;/p&gt;
&lt;p&gt;The Organization model is a part of &lt;strong&gt;Identity Management (IDM)&lt;/strong&gt;. PingIDM is the self-deployed instance and is a core feature set within AIC. Identity Management’s job is to represent, control, connect, and map identity data in and out of the Ping platform. Regarding Organizations, we care most about &lt;em&gt;representing&lt;/em&gt; and &lt;em&gt;controlling&lt;/em&gt; its data - what does the template look like, and ensuring that the data within it is managed appropriately (think CRUDPAQ - Create, Read, Update, Delete, Patch, Action, Query).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/idm-objects/appendix-managed-objects.html&quot;&gt;&lt;strong&gt;Managed Objects&lt;/strong&gt;&lt;/a&gt; are a templatized instance of data in IDM that contains &lt;strong&gt;Properties&lt;/strong&gt;. In essence, they are the representation of identities within IDM. By default, your instance comes with Users, Roles, Assignments, Groups, and Organizations, but you can create as many as you’d like. Managed Objects store data within a Directory, with each Managed Object being categorized separately.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/identities/user-identity-properties-attributes-reference.html&quot;&gt;&lt;strong&gt;Properties&lt;/strong&gt;&lt;/a&gt; (sometimes called Attributes) are the individual data fields within a Managed Object that represent the data types stored within that object. They define the specific pieces of information each instance of that Managed Object will hold. As an example, a User is a Managed Object, and their Username is one of their Properties. Properties can be booleans, numbers, arrays (called &lt;em&gt;multivalues&lt;/em&gt;), objects (including nested objects), strings, &lt;strong&gt;Relationships&lt;/strong&gt;, and &lt;strong&gt;Virtual Properties&lt;/strong&gt;. You can also &lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/idm-objects/creating-modifying-managed-objects.html#&quot;&gt;create custom types&lt;/a&gt; as needed. Properties have their own unique indexing&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, validation, hashing or encryption, and triggers which can be performed on actions such as validating, retrieving, or storing an instance of this Property.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/idm-objects/relationships-custom.html&quot;&gt;&lt;strong&gt;Relationships&lt;/strong&gt;&lt;/a&gt; are a type of Property that creates a linkage between instances of Managed Objects. Relationships can be one-to-one, one-to-many, many-to-one, and many-to-many, and can be linked to different Managed Objects or the same Managed Object. Common examples of relationships include Manager to Employees, Parent to Children, or Department to Teams. Relationships can contain their own data within the link itself, can return their data alongside either end of the link in a query, and can be used as a means to &lt;strong&gt;notify&lt;/strong&gt; the connected Managed Objects that changes have occurred on opposite ends of the link (&lt;strong&gt;Relationship-Derived Virtual Property&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/idm-objects/managed-object-virtual-properties.html&quot;&gt;&lt;strong&gt;Virtual Properties&lt;/strong&gt;&lt;/a&gt; are a powerful Property type that can dynamically calculate its own data on the fly, either on time of retrieval or based on notifications received from a Relationship tied to this Managed Object (&lt;strong&gt;Relationship-Derived Virtual Property&lt;/strong&gt;). This significantly simplifies data and queries as well as improves performance because the complex calculations are handled for you dynamically at run time or statically at storage time - for example, you could have a virtual property that dynamically returns the “Full Name” of a User as a combination of their First and Last name without actually having to store and update that value separately.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/idm-objects/managed-object-virtual-properties.html#relationship-derived-virtual-properties&quot;&gt;&lt;strong&gt;Relationship-Derived Virtual Properties (RDVPs)&lt;/strong&gt;&lt;/a&gt; are a type of Virtual Property that update based on notifications from the Relationships linked to its Managed Object. An RDVP has access to content from the Object’s Relationships and can travel the chain of relationships to create a composite value of the chain. Relationships and RDVPs are the two components of IDM that make Organizations so flexible and capable.&lt;/p&gt;
&lt;h2&gt;What’s the Deal with RDVPs?&lt;/h2&gt;
&lt;p&gt;RDVPs are an insanely powerful tool for reducing complexity and increasing performance.&lt;/p&gt;
&lt;p&gt;As an example, let’s say that you have a business structure of Managers to Employees like we mentioned in the Relationships section. In most companies, you’re not going to have one layer of Managers - you’ll probably have at least 3 and each of those layers may have tens to hundreds of Managers who then manage their own number of Employees.&lt;/p&gt;
&lt;p&gt;If I wanted to get a list of all of the Managers above an Employee, how would I go about it? Normally, it would likely be a series of queries that would look something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the Employee&lt;/li&gt;
&lt;li&gt;Find the Employee’s Manager&lt;/li&gt;
&lt;li&gt;Get the Manager&lt;/li&gt;
&lt;li&gt;Find the Manager’s Manager&lt;/li&gt;
&lt;li&gt;Get the Manager’s Manager&lt;/li&gt;
&lt;li&gt;Find the Manager’s Manager’s Manager&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;li&gt;Manager X has no Manager, return&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Think about this the other way, too. If I wanted to get all of the Employees &lt;em&gt;under&lt;/em&gt; a Manager, and each Manager has more than one Employee, how many operations do I have to take? Now what if I had to perform this action every time I entered an application? This is starting to sound a lot like one of those &lt;a href=&quot;https://www.geeksforgeeks.org/top-50-graph-coding-problems-for-interviews/&quot;&gt;classic graph traversal interview questions&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;The deeper or wider you go, with the more connections you have, the more operations you would normally have to take. This makes your code complex and significantly increases the number of queries you need to make to get the data you and your users need to be successful. &lt;strong&gt;RDVPs solve this problem.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RDVPs travel the Relationships for you and update a static result on the object directly at time of change from anywhere in the Relationship.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If we looked at the previous example, if I wanted to get a list of the Employee’s Managers, or a list of the Manager’s Employees, &lt;strong&gt;the data is on the User directly&lt;/strong&gt;. If the Employee is promoted, or switches teams, and the managerial hierarchy changes, &lt;strong&gt;the data on the User updates automatically&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RDVPs proactively updates relevant aggregate data so that you only have to look at one Object to understand what’s going on with everything it’s linked to.&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;What is the Organization Model?&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/identities/organizations.html&quot;&gt;&lt;strong&gt;Organizations&lt;/strong&gt;&lt;/a&gt; are a &lt;strong&gt;Managed Object&lt;/strong&gt; that contain multiple &lt;strong&gt;Relationships&lt;/strong&gt; both to Users and to other Organizations. These Relationships store &lt;strong&gt;RDVPs&lt;/strong&gt; that define the linkages that the Organization has with other Organizations and Users. Since Organizations are a Managed Object, they are adherent to the same scale as Users, meaning that AIC is rated for hundreds of millions of Organizations while PingIDM is hypothetically as scalable as your hardware will allow.&lt;/p&gt;
&lt;p&gt;Organizations, at their core, build a &lt;strong&gt;hierarchy&lt;/strong&gt; of templatized data. Due to the Relationships between Organizations and Users, this data can be &lt;strong&gt;inherited&lt;/strong&gt; up and down the hierarchy.&lt;/p&gt;
&lt;p&gt;Visualized, the Organization model often looks like a tree.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A hierarchy diagram providing an example of the org model&quot; loading=&quot;lazy&quot; width=&quot;1031&quot; height=&quot;1159&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/org-model-basic.BIqPt_JX_2mMkkq.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/org-model-basic.BIqPt_JX_Z1BmIoC.webp 640w, https://developer.pingidentity.com/blog/_astro/org-model-basic.BIqPt_JX_Z1eFPpW.webp 750w, https://developer.pingidentity.com/blog/_astro/org-model-basic.BIqPt_JX_1Pmh78.webp 828w, https://developer.pingidentity.com/blog/_astro/org-model-basic.BIqPt_JX_2mMkkq.webp 1031w&quot; /&gt;&amp;gt;
&lt;em&gt;The Organization Model&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Organizations enable hierarchical, inherited data templates&lt;/strong&gt;, allowing you to &lt;strong&gt;standardize and represent complex structures&lt;/strong&gt; such as families, business partnerships, and supplier/distributor models.&lt;/p&gt;
&lt;p&gt;But just having this data is only one part of what makes this model special. Organizations don’t just represent Parent/Child relationships between other Organizations: they also by default contain 3 different Relationship types with Users - and Users can retrieve and inherit information from the hierarchy as well. Due to the RDVPs already on the Organization, and the permission model in place on the Organization, elevated Relationship types (i.e. Owners and Admins) have the ability to manage aspects of both the Organization, its related Users, and any Organizations and Users that they’ve inherited - both via REST API and (in the case of AIC) hosted UI.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A hierarchy diagram providing an example of the org model with some users tied to it. Bubbles highlight the users to showcase their permissions to the organizations they&apos;re tied to&quot; loading=&quot;lazy&quot; width=&quot;1947&quot; height=&quot;1640&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/org-model-basic-users.r-GdUqqG_Z1Kfjs1.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/org-model-basic-users.r-GdUqqG_1bGYJQ.webp 640w, https://developer.pingidentity.com/blog/_astro/org-model-basic-users.r-GdUqqG_2c54VB.webp 750w, https://developer.pingidentity.com/blog/_astro/org-model-basic-users.r-GdUqqG_2gRAOD.webp 828w, https://developer.pingidentity.com/blog/_astro/org-model-basic-users.r-GdUqqG_1Qgcxd.webp 1080w, https://developer.pingidentity.com/blog/_astro/org-model-basic-users.r-GdUqqG_Kulua.webp 1280w, https://developer.pingidentity.com/blog/_astro/org-model-basic-users.r-GdUqqG_Z1m8chB.webp 1668w, https://developer.pingidentity.com/blog/_astro/org-model-basic-users.r-GdUqqG_Z1Kfjs1.webp 1947w&quot; /&gt;&amp;gt;
&lt;em&gt;The Organization Model with Users&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Organizations enable hierarchical, inherited delegated administration&lt;/strong&gt;, allowing you to &lt;strong&gt;scale administration alongside your platform&lt;/strong&gt; by delegating operational management of the hierarchy and its Users to the Users within the Organization (or its Parent).&lt;/p&gt;
&lt;p&gt;Additionally, these Relationship types are &lt;strong&gt;many to many&lt;/strong&gt;, meaning that a single User can be a Member, Admin, and Owner of as many different Organizations and Hierarchies as they need.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A hierarchy diagram providing an example of the org model where a user is linked to more than one organization hierarchy&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1346&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/org-model-universal-identity.CXlNwMkZ_sFD9C.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/org-model-universal-identity.CXlNwMkZ_Z2a8qbA.webp 640w, https://developer.pingidentity.com/blog/_astro/org-model-universal-identity.CXlNwMkZ_vEvs5.webp 750w, https://developer.pingidentity.com/blog/_astro/org-model-universal-identity.CXlNwMkZ_1LkMJ.webp 828w, https://developer.pingidentity.com/blog/_astro/org-model-universal-identity.CXlNwMkZ_ZGY9Kp.webp 1080w, https://developer.pingidentity.com/blog/_astro/org-model-universal-identity.CXlNwMkZ_Z2gVfIO.webp 1280w, https://developer.pingidentity.com/blog/_astro/org-model-universal-identity.CXlNwMkZ_Z29J2zk.webp 1668w, https://developer.pingidentity.com/blog/_astro/org-model-universal-identity.CXlNwMkZ_sFD9C.webp 1999w&quot; /&gt;&amp;gt;&lt;br /&gt;
&lt;em&gt;Universal Identity in Organizations&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Organizations enable a Universal Identity&lt;/strong&gt;, where &lt;strong&gt;one Identity has one User account for all Services&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;So, why Organizations?&lt;/p&gt;
&lt;p&gt;Organizations let you:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Templatize grouping of content so that you can build a standard that the platform adheres to&lt;/li&gt;
&lt;li&gt;Delegate the content so that the platform can be managed at scale without having to hire thousands of admins to keep up&lt;/li&gt;
&lt;li&gt;Link the content so that it and its users can inherit information and administration for quicker time-to-value&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;A Quick Analogy&lt;/h2&gt;
&lt;p&gt;Think of the Organization Model as a Condominium.&lt;/p&gt;
&lt;p&gt;As the Tenant Administrator, you’re the owner of the Building itself. You dictate what services are available, how the condos are laid out, and the keycard system for how the residents get into the building. You can also add floors and rooms to the Condo at any time.&lt;/p&gt;
&lt;p&gt;The people in your Condo (Users) could be Residents (Admins) or Staff (Owners). Everyone must use their keycard to get into the building, and that same keycard may give them access to different rooms or Condos in the building. A Resident, for example, may have access to their Condo as well as to a friend’s Condo. Staff may have access to all Condos on a given floor, and may have access to systems like plumbing and electrical which the Residents don’t interact with.&lt;/p&gt;
&lt;p&gt;Residents can modify their Condo, but how they can change it must adhere to the rules and regulations defined by the Building. To the benefit of the Resident, ongoing maintenance of the Condo and its services are managed by the Building and not by them.&lt;/p&gt;
&lt;p&gt;If the Resident decides to rent out their Condo, their Tenants (Members) will have access to the Building and to the Condo but won’t be able to modify the Condo at all. They can only use the facilities given to them by the Resident.&lt;/p&gt;
&lt;p&gt;As your Building grows, you may need more staff to run it. As such, you can appoint Floor Managers (Owners of a Parent Org) who also have the ability to create new Condos and manage the Floors,  Residents, and Tenants within.&lt;/p&gt;
&lt;p&gt;Additionally, some of your Maintenance Staff and Floor Managers live in your Building. That means they are also Residents, and have different capabilities and responsibilities depending on what Condo they’re currently in.&lt;/p&gt;
&lt;h1&gt;Utilizing the Organization Model&lt;/h1&gt;
&lt;p&gt;So let’s put this all together and see how the Organization Model works in practice. Note that the platform screens shown below are taken from the built-in Hosted Pages within AIC, however each and every action is available in PingIDM via the Native UI or REST API.&lt;/p&gt;
&lt;p&gt;Our goal today will be to create a basic business hierarchy. We’ll take the Supplier/Distributor model for this example.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A hierarchy diagram providing an example of the org model related to a supplier-distributor model. Suppliers and distributors are located below BX Manufacturing, and Supplier A and B are underneath Suppliers&quot; loading=&quot;lazy&quot; width=&quot;1627&quot; height=&quot;1177&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/org-model-sd.u9LitBId_ZReDI4.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/org-model-sd.u9LitBId_ZaeQrG.webp 640w, https://developer.pingidentity.com/blog/_astro/org-model-sd.u9LitBId_Z1GkCHg.webp 750w, https://developer.pingidentity.com/blog/_astro/org-model-sd.u9LitBId_Z11ILB3.webp 828w, https://developer.pingidentity.com/blog/_astro/org-model-sd.u9LitBId_1VOAN2.webp 1080w, https://developer.pingidentity.com/blog/_astro/org-model-sd.u9LitBId_1MtXMx.webp 1280w, https://developer.pingidentity.com/blog/_astro/org-model-sd.u9LitBId_ZReDI4.webp 1627w&quot; /&gt;&amp;gt;&lt;br /&gt;
&lt;em&gt;Supplier/Distributor Example&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In our example we have a Manufacturing vertical named &lt;strong&gt;BX Manufacturing&lt;/strong&gt;. BX Manufacturing has partnerships with &lt;strong&gt;Suppliers&lt;/strong&gt;, who provide goods and services, and &lt;strong&gt;Distributors&lt;/strong&gt;, who purchase BX Manufacturing’s products to then be sold elsewhere. Both Suppliers and Distributors need to give their employees access to BX Manufacturing’s systems but what they ultimately do with those systems is very different.&lt;/p&gt;
&lt;p&gt;To start, let’s create the Root Organization. As the Tenant Admin, we can select “New Organization”, enter in the Name, and then we’re ready to go.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the managed identities page in which the new organization button is highlighted&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1242&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/create-org.do-882lI_Z1b1sHM.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/create-org.do-882lI_2mfGwo.webp 640w, https://developer.pingidentity.com/blog/_astro/create-org.do-882lI_Z2taYau.webp 750w, https://developer.pingidentity.com/blog/_astro/create-org.do-882lI_waIbS.webp 828w, https://developer.pingidentity.com/blog/_astro/create-org.do-882lI_ZWiFkv.webp 1080w, https://developer.pingidentity.com/blog/_astro/create-org.do-882lI_ZRhUHi.webp 1280w, https://developer.pingidentity.com/blog/_astro/create-org.do-882lI_Z28JIjw.webp 1668w, https://developer.pingidentity.com/blog/_astro/create-org.do-882lI_Z1b1sHM.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the managed identities page in which the the name BX Manufacturing is entered&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1211&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/create-org-name.BDSLQqPf_Z5b4pT.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/create-org-name.BDSLQqPf_22abLO.webp 640w, https://developer.pingidentity.com/blog/_astro/create-org-name.BDSLQqPf_Z12m5pb.webp 750w, https://developer.pingidentity.com/blog/_astro/create-org-name.BDSLQqPf_ZgXEwr.webp 828w, https://developer.pingidentity.com/blog/_astro/create-org-name.BDSLQqPf_Z1wF8s3.webp 1080w, https://developer.pingidentity.com/blog/_astro/create-org-name.BDSLQqPf_823m6.webp 1280w, https://developer.pingidentity.com/blog/_astro/create-org-name.BDSLQqPf_1rDgq9.webp 1668w, https://developer.pingidentity.com/blog/_astro/create-org-name.BDSLQqPf_Z5b4pT.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the managed identities page in which the new org BX Manufacturing is selected and created&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1313&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/create-org-page.C_qhGLe-_UPXP8.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/create-org-page.C_qhGLe-_cj6BH.webp 640w, https://developer.pingidentity.com/blog/_astro/create-org-page.C_qhGLe-_jCaKE.webp 750w, https://developer.pingidentity.com/blog/_astro/create-org-page.C_qhGLe-_Z1oEohy.webp 828w, https://developer.pingidentity.com/blog/_astro/create-org-page.C_qhGLe-_Z2jBcpd.webp 1080w, https://developer.pingidentity.com/blog/_astro/create-org-page.C_qhGLe-_295HpB.webp 1280w, https://developer.pingidentity.com/blog/_astro/create-org-page.C_qhGLe-_bbJB8.webp 1668w, https://developer.pingidentity.com/blog/_astro/create-org-page.C_qhGLe-_UPXP8.webp 1999w&quot; /&gt;&amp;gt;&lt;br /&gt;
&lt;em&gt;Creating the Root Organization&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now, an Organization is only as useful as its Relationships. With that in mind, let’s assign an Owner to this Organization so that we can delegate management of the Manufacturing Vertical and its subdivisions.&lt;/p&gt;
&lt;p&gt;Underneath the “Owner” section, we can assign a User to our Organization. In my example, the user “Owner” has already been registered. Note that while we are assigning manually here, you can always automate this assignment during provisioning, user registration (likely via a Journey/Tree in AIC/PingAM), or (if you have Governance features in your AIC tenant) during Access Request or with a Form submission that triggers a Workflow or Approval.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the BX Manufacturing Org with the Add Owner button highlighted&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1313&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/add-owner.CW5LGMni_9Bb3v.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/add-owner.CW5LGMni_Z2fR1aE.webp 640w, https://developer.pingidentity.com/blog/_astro/add-owner.CW5LGMni_Z1ctI29.webp 750w, https://developer.pingidentity.com/blog/_astro/add-owner.CW5LGMni_m6q48.webp 828w, https://developer.pingidentity.com/blog/_astro/add-owner.CW5LGMni_Z2U0X9.webp 1080w, https://developer.pingidentity.com/blog/_astro/add-owner.CW5LGMni_D8CLj.webp 1280w, https://developer.pingidentity.com/blog/_astro/add-owner.CW5LGMni_1OYfVH.webp 1668w, https://developer.pingidentity.com/blog/_astro/add-owner.CW5LGMni_9Bb3v.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the BX Manufacturing Org with the owner &amp;quot;owner&amp;quot; being assigned&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1313&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/add-owner-assign.CrPg0DFo_as04G.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/add-owner-assign.CrPg0DFo_Z2mMEkK.webp 640w, https://developer.pingidentity.com/blog/_astro/add-owner-assign.CrPg0DFo_ZFTDla.webp 750w, https://developer.pingidentity.com/blog/_astro/add-owner-assign.CrPg0DFo_Z1ywXwf.webp 828w, https://developer.pingidentity.com/blog/_astro/add-owner-assign.CrPg0DFo_1gI185.webp 1080w, https://developer.pingidentity.com/blog/_astro/add-owner-assign.CrPg0DFo_wB799.webp 1280w, https://developer.pingidentity.com/blog/_astro/add-owner-assign.CrPg0DFo_Z1DXsac.webp 1668w, https://developer.pingidentity.com/blog/_astro/add-owner-assign.CrPg0DFo_as04G.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the BX Manufacturing Org with the &amp;quot;owner&amp;quot; user assigned as owner&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1313&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/add-owner-assigned.9-m0b0qj_ZaWQ5n.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/add-owner-assigned.9-m0b0qj_Z1oS524.webp 640w, https://developer.pingidentity.com/blog/_astro/add-owner-assigned.9-m0b0qj_Z1yzzKb.webp 750w, https://developer.pingidentity.com/blog/_astro/add-owner-assigned.9-m0b0qj_Z1oAUXl.webp 828w, https://developer.pingidentity.com/blog/_astro/add-owner-assigned.9-m0b0qj_Ze6z7r.webp 1080w, https://developer.pingidentity.com/blog/_astro/add-owner-assigned.9-m0b0qj_2h7qhR.webp 1280w, https://developer.pingidentity.com/blog/_astro/add-owner-assigned.9-m0b0qj_1kxgYJ.webp 1668w, https://developer.pingidentity.com/blog/_astro/add-owner-assigned.9-m0b0qj_ZaWQ5n.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Assigning the Owner&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let’s log in as the Owner User. You’ll find that you have access to update and delete BX Manufacturing and its users and can create new Organizations underneath the Organization you’re an Owner of.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the owner logged in and viewing the Org BX Manufacturing&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;916&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-permission.DvmKdvw5_Zb6YVJ.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-permission.DvmKdvw5_1VfxrA.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-permission.DvmKdvw5_kGVCf.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-permission.DvmKdvw5_Z1W6gMF.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-permission.DvmKdvw5_Z1zw1Il.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-permission.DvmKdvw5_Z2tasUL.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-permission.DvmKdvw5_ZKFOQK.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-permission.DvmKdvw5_Zb6YVJ.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the owner logged in and viewing the Org BX Manufacturing&apos;s details&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-permission-details.CaO0dst4_ZV5cNj.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-permission-details.CaO0dst4_Z1QCheL.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-permission-details.CaO0dst4_7e2g2.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-permission-details.CaO0dst4_Z2hTMdz.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-permission-details.CaO0dst4_Z1i3OF0.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-permission-details.CaO0dst4_ZLli4s.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-permission-details.CaO0dst4_1nyRih.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-permission-details.CaO0dst4_ZV5cNj.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Default Owner Permissions&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While you have very similar capabilities to the Tenant Administrator by default, you won’t have access to manage other Owners - that’s the job of the Tenant Administrator.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the owner logged in and viewing the Org BX Manufacturing in which they cannot see or edit other owners&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-permission-owners.CLtBIbhc_Z9TS5k.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-permission-owners.CLtBIbhc_10TFSz.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-permission-owners.CLtBIbhc_jL9MB.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-permission-owners.CLtBIbhc_lwRCT.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-permission-owners.CLtBIbhc_1BVsN6.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-permission-owners.CLtBIbhc_1GVY6p.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-permission-owners.CLtBIbhc_1lEYpU.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-permission-owners.CLtBIbhc_Z9TS5k.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Default Owner Permissions - No Owner Management&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Since we’ve been delegated ownership of BX Manufacturing, let’s set up the Suppliers and Distributors suborganizations and assign some people to manage them. We’ll create a new Organization and assign that Organization to BX Manufacturing as a Parent.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the owner selecting the new organization button&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-org.DFseNM5b_mUXRd.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-org.DFseNM5b_10HliX.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-create-org.DFseNM5b_ZsnoRk.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-create-org.DFseNM5b_preUz.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-create-org.DFseNM5b_ZS46g8.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-create-org.DFseNM5b_h0t36.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-create-org.DFseNM5b_Z1XYNlL.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-create-org.DFseNM5b_mUXRd.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the owner entering the name &amp;quot;Suppliers&amp;quot; and associating it to the parent &amp;quot;BX Manufacturing&amp;quot;&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-org-assign.BzC5r5gr_Z1AyAwq.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-org-assign.BzC5r5gr_1i5YJ7.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-create-org-assign.BzC5r5gr_AWsD9.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-create-org-assign.BzC5r5gr_CIbtr.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-create-org-assign.BzC5r5gr_bhKm0.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-create-org-assign.BzC5r5gr_gigEj.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-create-org-assign.BzC5r5gr_Z4XI1b.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-create-org-assign.BzC5r5gr_Z1AyAwq.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the owner viewing the Suppliers org they created&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-org-result.DvZ2bSHs_13w0YX.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-org-result.DvZ2bSHs_1njW0T.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-create-org-result.DvZ2bSHs_GbpTV.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-create-org-result.DvZ2bSHs_HW8Ke.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-create-org-result.DvZ2bSHs_Z2eNKUx.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-create-org-result.DvZ2bSHs_Z29NfCe.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-create-org-result.DvZ2bSHs_Z2v5fiI.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-create-org-result.DvZ2bSHs_13w0YX.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Creating the Sub-Organizations (Children)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We’ll repeat the same action for the Distributors Organization.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the owner viewing the organizations they have access to - BX Manufacturing, Suppliers, and Distributors&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-orgs.B7eYQLGa_ZpVGYY.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-orgs.B7eYQLGa_1BprRg.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-create-orgs.B7eYQLGa_ZqvN3m.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-create-orgs.B7eYQLGa_Z1NEoPP.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-create-orgs.B7eYQLGa_1ncFMc.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-create-orgs.B7eYQLGa_Z7oJRl.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-create-orgs.B7eYQLGa_Z1kN031.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-create-orgs.B7eYQLGa_ZpVGYY.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Creating the First-Level Children&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Currently, our Organizations don’t have any Users in them. Let’s fix that. As an Owner, we can create a new User and assign them as a Member and/or an Administrator to any and all Organizations that we own and have inherited ownership to.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the owner selecting the new user button&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-member.Byig4aGP_Z1DlGWh.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-member.Byig4aGP_Z10ggXv.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-create-member.Byig4aGP_Z9tilI.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-create-member.Byig4aGP_2oJm5M.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-create-member.Byig4aGP_Z2jnuhq.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-create-member.Byig4aGP_Z6xxss.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-create-member.Byig4aGP_KVa3k.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-create-member.Byig4aGP_Z1DlGWh.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the owner adding in the user details and assigning that user to an org as a member&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-member-assign.uLySBTx9_Z1dt3to.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-member-assign.uLySBTx9_2i1zAc.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-create-member-assign.uLySBTx9_urBKy.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-create-member-assign.uLySBTx9_26WM6f.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-create-member-assign.uLySBTx9_1qU7HB.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-create-member-assign.uLySBTx9_Z14QDCx.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-create-member-assign.uLySBTx9_Gka90.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-create-member-assign.uLySBTx9_Z1dt3to.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the owner assigning the new user to be an admin to an organization by selecting the &amp;quot;Add Organizations I Administer&amp;quot; button&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-admin.o3a6ugvP_Z1ATy7a.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-admin.o3a6ugvP_ZhW3zH.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-create-admin.o3a6ugvP_hICIf.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-create-admin.o3a6ugvP_ZIR5P.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-create-admin.o3a6ugvP_Z1iId9t.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-create-admin.o3a6ugvP_WGiHY.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-create-admin.o3a6ugvP_1xUjPN.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-create-admin.o3a6ugvP_Z1ATy7a.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the owner assigning the user to be an admin of the Suppliers org&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-admin-assign.YzmliaLW_5rkVI.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-create-admin-assign.YzmliaLW_1KoNI2.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-create-admin-assign.YzmliaLW_7MSYP.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-create-admin-assign.YzmliaLW_Z1FzGjH.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-create-admin-assign.YzmliaLW_ZDPKHn.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-create-admin-assign.YzmliaLW_Z1NR0iw.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-create-admin-assign.YzmliaLW_1Uf6qj.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-create-admin-assign.YzmliaLW_5rkVI.webp 1999w&quot; /&gt;&amp;gt;&lt;br /&gt;
&lt;em&gt;Creating the Suppliers Administrator&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If we log out as an Owner, and log in as the Suppliers Admin, we’ll see that we only have access to the Organization that we were assigned, and unlike the Owner can’t manage other Administrators of our Organization(s).&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the administrator&apos;s permissions to the Suppliers org only&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-permission.CqminG9b_tUhir.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-permission.CqminG9b_Z26bkS5.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-permission.CqminG9b_1uU2Jy.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-permission.CqminG9b_2nJGxs.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-permission.CqminG9b_ZL4MOT.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-permission.CqminG9b_nYLtk.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-permission.CqminG9b_Z1R0uUx.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-permission.CqminG9b_tUhir.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the administrator&apos;s permissions to the Suppliers org where they cannot add or edit admins&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-permission-admins.BxXgX6xr_1YG6Lj.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-permission-admins.BxXgX6xr_Z2bXb1w.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-permission-admins.BxXgX6xr_2c5qGr.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-permission-admins.BxXgX6xr_2dQ9wJ.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-permission-admins.BxXgX6xr_Z1iDF9c.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-permission-admins.BxXgX6xr_Z1dD9PS.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-permission-admins.BxXgX6xr_Z1yU9wn.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-permission-admins.BxXgX6xr_1YG6Lj.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Default Administrator Permissions&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We &lt;em&gt;can&lt;/em&gt;, however, manage our Users and create and manage new Organizations. Let’s create Supplier A and Supplier B now as children of Suppliers.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the administrator&apos;s portal in which they have created Supplier A and Supplier B and assigned them to the Suppliers Org as children&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-create-orgs.CbGI9VdX_ZyJlVH.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-create-orgs.CbGI9VdX_Z1uYiaG.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs.CbGI9VdX_1wgzHC.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs.CbGI9VdX_97XU9.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs.CbGI9VdX_1ep1Pt.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs.CbGI9VdX_ZgcoO4.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs.CbGI9VdX_Z1tADYJ.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs.CbGI9VdX_ZyJlVH.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the administrator&apos;s view of Supplier B in which the org has been assigned&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-create-orgs-assign.BJFK7TEN_Z2rJDUq.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-create-orgs-assign.BJFK7TEN_3s4Xq.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs-assign.BJFK7TEN_22jote.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs-assign.BJFK7TEN_ZmOq0n.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs-assign.BJFK7TEN_2gsR1O.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs-assign.BJFK7TEN_Z2i0Jbz.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs-assign.BJFK7TEN_Z85yNP.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-create-orgs-assign.BJFK7TEN_Z2rJDUq.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Creating the Suppliers&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Additionally, let’s create some Users who are Members of the different Suppliers.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the administrator&apos;s user view in which the Supplier A Member and Supplier B Member have been created&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-create-users.DSNBqUlZ_Z2qaGc2.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-create-users.DSNBqUlZ_28vTvA.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-create-users.DSNBqUlZ_Z2lYwYo.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-create-users.DSNBqUlZ_2pJ60s.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-create-users.DSNBqUlZ_Z27Ylel.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-create-users.DSNBqUlZ_8qaD7.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-create-users.DSNBqUlZ_IEbKV.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-create-users.DSNBqUlZ_Z2qaGc2.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the administrator&apos;s view into Supplier A where they have assigned Supplier A Member&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-assign-member-a.BjCEcGYa_28BDIi.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-assign-member-a.BjCEcGYa_Z2eDW4A.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-assign-member-a.BjCEcGYa_18a86U.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-assign-member-a.BjCEcGYa_1UQ1Kw.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-assign-member-a.BjCEcGYa_ZooEVC.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-assign-member-a.BjCEcGYa_ZvTeTf.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-assign-member-a.BjCEcGYa_1iQUlC.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-assign-member-a.BjCEcGYa_28BDIi.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Assigning Members&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Log back in as the Owner. You’ll see that you have inherited permissions to the Suppliers and the Users made by your delegated Administrator.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the owners view in which they now have visibility into the newly created orgs&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-view-tree.DGp_O5w8_1ixG18.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-view-tree.DGp_O5w8_27qWuR.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-view-tree.DGp_O5w8_19tVrP.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-view-tree.DGp_O5w8_Z1alJES.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-view-tree.DGp_O5w8_Zdu3G8.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-view-tree.DGp_O5w8_1FQd77.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-view-tree.DGp_O5w8_Z25fKgX.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-view-tree.DGp_O5w8_1ixG18.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the owners view in which they now have visibility into the newly created users underneath the orgs they now manage&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/owner-view-tree-members.BMUpkf_A_Z1xURJf.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/owner-view-tree-members.BMUpkf_A_ZQWbt.webp 640w, https://developer.pingidentity.com/blog/_astro/owner-view-tree-members.BMUpkf_A_ZH0thr.webp 750w, https://developer.pingidentity.com/blog/_astro/owner-view-tree-members.BMUpkf_A_ZFeKr9.webp 828w, https://developer.pingidentity.com/blog/_astro/owner-view-tree-members.BMUpkf_A_dUt9b.webp 1080w, https://developer.pingidentity.com/blog/_astro/owner-view-tree-members.BMUpkf_A_iUYru.webp 1280w, https://developer.pingidentity.com/blog/_astro/owner-view-tree-members.BMUpkf_A_Z2l0e0.webp 1668w, https://developer.pingidentity.com/blog/_astro/owner-view-tree-members.BMUpkf_A_Z1xURJf.webp 1999w&quot; /&gt;&amp;gt;&lt;br /&gt;
&lt;em&gt;Inheritance at Work&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Owners and Admins&lt;/strong&gt; &lt;strong&gt;inherit permission&lt;/strong&gt; to all of their sub-Organizations and its Users. &lt;strong&gt;Members inherit context&lt;/strong&gt; from all of their Parent Organizations. &lt;strong&gt;Owners and Admins permission goes down the tree, Members context goes up.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We mentioned before that Users can have more than one Relationship to one or more Organization. Let’s say, for example, that Supplier B interacts with Distributor A to sell OEM add-ons to a customer, like a spoiler on a car. Since BX Manufacturing is the creator and owner of the finished product, they must be intrinsically involved in the linkage between this Supplier and Distributor. Here we’re dealing with a &lt;strong&gt;B2B2C&lt;/strong&gt; scenario, or Business-to-Business-to-Consumer.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;An example diagram in which one identity is being used across multiple organizations in different capacities&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1400&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/unified-identity.DekWYXYB_wPlYu.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/unified-identity.DekWYXYB_Z29LTwC.webp 640w, https://developer.pingidentity.com/blog/_astro/unified-identity.DekWYXYB_rwacc.webp 750w, https://developer.pingidentity.com/blog/_astro/unified-identity.DekWYXYB_2fjhW1.webp 828w, https://developer.pingidentity.com/blog/_astro/unified-identity.DekWYXYB_Z16vX7q.webp 1080w, https://developer.pingidentity.com/blog/_astro/unified-identity.DekWYXYB_ZePy1b.webp 1280w, https://developer.pingidentity.com/blog/_astro/unified-identity.DekWYXYB_Z2wWEHV.webp 1668w, https://developer.pingidentity.com/blog/_astro/unified-identity.DekWYXYB_wPlYu.webp 1999w&quot; /&gt;&amp;gt;&lt;br /&gt;
&lt;em&gt;Unified Identity&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this situation, our User is an Admin to Supplier B and is also a Member to Distributor A. At the end of the day, this is the same person with the same account and the same login but with different relationships to each Organization. One person is tied to one identity, but the Relationships that they have to the Organizations give them different permissions to Organizations and Users and give others different permissions to them.&lt;/p&gt;
&lt;p&gt;In practice, as the user Supplier B Admin I can see Supplier B and the Members associated with Supplier B. &lt;strong&gt;As Distributor A Admin, I can see and manage Supplier B Admin because they’re a Member of my Organization.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My Supplier received the context they needed to interact with Distributor while still being able to manage their own Users and Supplier information. My Distributor cannot receive the context of the Supplier but can manage the suppliers associated with their Organization.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the supplier B admin&apos;s view in which they can access Supplier B&apos;s organization&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/supplier-admin-org.Bbxlui3j_1mb2rJ.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/supplier-admin-org.Bbxlui3j_ZQ8hjn.webp 640w, https://developer.pingidentity.com/blog/_astro/supplier-admin-org.Bbxlui3j_ZgrA0q.webp 750w, https://developer.pingidentity.com/blog/_astro/supplier-admin-org.Bbxlui3j_ZyU5Ov.webp 828w, https://developer.pingidentity.com/blog/_astro/supplier-admin-org.Bbxlui3j_1Emnpq.webp 1080w, https://developer.pingidentity.com/blog/_astro/supplier-admin-org.Bbxlui3j_Z19pew3.webp 1280w, https://developer.pingidentity.com/blog/_astro/supplier-admin-org.Bbxlui3j_Zybdoe.webp 1668w, https://developer.pingidentity.com/blog/_astro/supplier-admin-org.Bbxlui3j_1mb2rJ.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the supplier B admin&apos;s view in which they can access Supplier B&apos;s members&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/supplier-admin-members.BohdOL1Q_Z289FF5.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/supplier-admin-members.BohdOL1Q_m0tIE.webp 640w, https://developer.pingidentity.com/blog/_astro/supplier-admin-members.BohdOL1Q_1dF8DP.webp 750w, https://developer.pingidentity.com/blog/_astro/supplier-admin-members.BohdOL1Q_2jhdW9.webp 828w, https://developer.pingidentity.com/blog/_astro/supplier-admin-members.BohdOL1Q_GWt6M.webp 1080w, https://developer.pingidentity.com/blog/_astro/supplier-admin-members.BohdOL1Q_Z2UvyH.webp 1280w, https://developer.pingidentity.com/blog/_astro/supplier-admin-members.BohdOL1Q_Z1sbjuR.webp 1668w, https://developer.pingidentity.com/blog/_astro/supplier-admin-members.BohdOL1Q_Z289FF5.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the distributor A admin&apos;s view in which they can access Distributor A&apos;s organization&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/distributor-admin-org.B2OYWhRS_Z1c4o0T.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/distributor-admin-org.B2OYWhRS_qRSSM.webp 640w, https://developer.pingidentity.com/blog/_astro/distributor-admin-org.B2OYWhRS_Z1gu9ID.webp 750w, https://developer.pingidentity.com/blog/_astro/distributor-admin-org.B2OYWhRS_ZsNg52.webp 828w, https://developer.pingidentity.com/blog/_astro/distributor-admin-org.B2OYWhRS_1l6q87.webp 1080w, https://developer.pingidentity.com/blog/_astro/distributor-admin-org.B2OYWhRS_1dAQau.webp 1280w, https://developer.pingidentity.com/blog/_astro/distributor-admin-org.B2OYWhRS_Z21O7nz.webp 1668w, https://developer.pingidentity.com/blog/_astro/distributor-admin-org.B2OYWhRS_Z1c4o0T.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the distributor A admin&apos;s view in which they can access Distributor A&apos;s members, which includes Supplier B Admin&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1031&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/distributor-admin-members.BDuMc3Si_o747X.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/distributor-admin-members.BDuMc3Si_Z1P84iA.webp 640w, https://developer.pingidentity.com/blog/_astro/distributor-admin-members.BDuMc3Si_1Bs9M9.webp 750w, https://developer.pingidentity.com/blog/_astro/distributor-admin-members.BDuMc3Si_ZbUqwo.webp 828w, https://developer.pingidentity.com/blog/_astro/distributor-admin-members.BDuMc3Si_Zlb2w8.webp 1080w, https://developer.pingidentity.com/blog/_astro/distributor-admin-members.BDuMc3Si_Z1vch7h.webp 1280w, https://developer.pingidentity.com/blog/_astro/distributor-admin-members.BDuMc3Si_2dTOBy.webp 1668w, https://developer.pingidentity.com/blog/_astro/distributor-admin-members.BDuMc3Si_o747X.webp 1999w&quot; /&gt;&amp;gt;&lt;br /&gt;
&lt;em&gt;B2B2C Using Organizations&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Referencing the Organization Model in the Platform&lt;/h1&gt;
&lt;p&gt;Advanced Identity Cloud takes the Organization Model one step further and incorporates it throughout its platform. That way, actions and interactions can reference the context and relationships to make unique experiences and define specific permissions defined by each individual user.&lt;/p&gt;
&lt;p&gt;To start, the &lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/idm-scripting/scripting-func-engine.html&quot;&gt;openidm binding&lt;/a&gt; allows you to access and manage any IDM data throughout AIC. This gives you the flexibility to interact with your Organization throughout the User journey, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An Event Hook that auto-assigns Organization membership based on a Property or Group they’re a member of (for example, a specific AD OU)&lt;/li&gt;
&lt;li&gt;A Custom Endpoint that returns Relationship information for decentralized Policy Enforcement&lt;/li&gt;
&lt;li&gt;User Journeys where things like branding, MFA, and risk policies are dictated by the configuration on the Organization&lt;/li&gt;
&lt;li&gt;Access Token Modification where the token includes context of what Organizational privileges the User has&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To give you a sense of what actions are useful in the context of Organizations, take a look at this &lt;a href=&quot;https://github.com/gwizdala/lib-ping/blob/main/Library%20Scripts/library_org.js&quot;&gt;Library Script&lt;/a&gt; that you can use wherever Next-Gen scripting is available.&lt;/p&gt;
&lt;p&gt;If you have Governance enabled within your AIC Tenant, Organizations appear in the following places:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Owners and Admins can request access to Applications, Entitlements, and Roles on behalf of their Members.&lt;/li&gt;
&lt;li&gt;Identity Certification Campaigns can be filtered to Users in specific Organizations&lt;/li&gt;
&lt;li&gt;Scopes can be filtered to specific Organizations and Relationships (e.g. Owner/Admin/Member)&lt;/li&gt;
&lt;li&gt;Forms allow a dynamically enumerated list of Organizations as a Multi-select option&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just like with the rest of the platform, you can utilize the same bindings to interact with the Organization in actions such as Workflows.&lt;/p&gt;
&lt;h1&gt;Extending the Organization Model&lt;/h1&gt;
&lt;p&gt;So where do we go from here?&lt;/p&gt;
&lt;p&gt;Organizations give us a template with pre-built relationships that can be referenced throughout Ping’s platform. And that’s just it: it’s a &lt;strong&gt;template&lt;/strong&gt; upon which we can extend and expand to meet the needs of our organization.&lt;/p&gt;
&lt;p&gt;As a start, add Properties to your Organization - things you see your business units and partnerships doing over and over again that could (and should) be standardized.&lt;/p&gt;
&lt;p&gt;Once you’ve built that model the way you like it, connect the model to your platform. It could be &lt;a href=&quot;https://gwizkid.com/posts/dynamically-branding-journeys-in-aic/&quot;&gt;a way to control branding&lt;/a&gt;, &lt;a href=&quot;https://gwizkid.com/posts/managing-mfa-in-journeys/&quot;&gt;extended support capabilities&lt;/a&gt;, or something completely different - work with your stakeholders to decide what is important for them and their users to &lt;strong&gt;have&lt;/strong&gt; and to &lt;strong&gt;manage&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When you’ve got the model in a place you like, &lt;a href=&quot;https://gwizkid.com/posts/setting-delegated-admin-privileges-in-aic/&quot;&gt;delegate administration&lt;/a&gt; to the capabilities you’d like to share with your partnerships. You own the model, and you delegate the controls.&lt;/p&gt;
&lt;p&gt;If you see that the parent organizations need more control than the children, consider &lt;a href=&quot;https://gwizkid.com/posts/modeling-inheritance-in-aic-or-idm/&quot;&gt;applying inheritance rules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And, as you expand, keep changing it! The beauty of this template is that it grows with you: you can enable the right experience, security, lifecycle, and overall IAM strategy for each and every use case without having to rebuild your entire platform every time a new business requirement comes along.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Through this Guide we have:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Learned about the key elements of IDM that make up the Organization model&lt;/li&gt;
&lt;li&gt;Learned how the Organization model applies these concept&lt;/li&gt;
&lt;li&gt;Created, delegated, and managed Organizations and Users&lt;/li&gt;
&lt;li&gt;Learned how to connect Organizations throughout AIC&lt;/li&gt;
&lt;li&gt;Learned about common approaches in extending the Organization model to meet our business needs&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Organization model provides you the bricks to build dynamic, magical experiences for your Administrators and your Users. And with this foundation the only limit you have is your imagination.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do you have thoughts or questions on this article? Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Note that AIC provides &lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/identities/identity-cloud-identity-schema.html#use-general-purpose-extension-attributes&quot;&gt;general purpose extension attributes&lt;/a&gt; for cases in which you need to index additional values as a means to protect tenant response time. This is not a limitation in PingIDM. &lt;a href=&quot;#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.BCZOVrjw_3pGez.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>PingOne DaVinci Primer: Creating Dynamic Experiences with PingOne Populations</title><link>https://developer.pingidentity.com/blog/davinci-primer-dynamic-populations/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/davinci-primer-dynamic-populations/</guid><description>Learn how to build dynamic experiences using PingOne context in PingOne DaVinci</description><pubDate>Fri, 22 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This post was originally published on &lt;a href=&quot;https://gwizkid.com/posts/davinci-primer-dynamic-populations/&quot;&gt;Gwiz Kid&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;As part of creating a User in PingOne that User will need to be assigned to a &lt;a href=&quot;https://docs.pingidentity.com/pingone/directory/p1_populations.html&quot;&gt;Population&lt;/a&gt;. Populations in PingOne create logical segments of user identities, which not only define administrative boundaries for Users and Groups with elevated permissions (Admin Roles) but also provide additional metadata that can be used to cater the User’s experience to fit their unique use cases.&lt;/p&gt;
&lt;p&gt;This How-To will teach you how to create dynamic user experiences using PingOne’s built-in Populations coupled with PingOne DaVinci. Along the way you’ll learn common strategies and design decisions when building out PingOne DaVinci flows.&lt;/p&gt;
&lt;p&gt;The level of detail here is meant for beginners to PingOne and PingOne DaVinci. If you are just looking for, or want to follow along with, the pre-built example, download the DaVinci flow using the link &lt;a href=&quot;https://github.com/gwizdala/lib-ping/blob/main/How-Tos/davinci-primer-dynamic-populations/dynamic_experiences_with_populations.json&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are DaVinci tips and tricks interspersed throughout this document. Search for the ⭐ emoji to jump between them.&lt;/p&gt;
&lt;p&gt;This How-To is broken down into the following sections&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#initial-setup&quot;&gt;Initial Setup&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#creating-the-forms&quot;&gt;Creating the Forms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#creating-your-davinci-flow&quot;&gt;Creating Your DaVinci Flow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#retrieving-the-population&quot;&gt;Retrieving the Population&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#retrieving-the-population-based-on-the-user&quot;&gt;Retrieving the Population based on the User&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#collecting-the-username&quot;&gt;Collecting the Username&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#finding-the-user&quot;&gt;Finding the User&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#retrieving-the-population-details&quot;&gt;Retrieving the Population Details&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#retrieving-the-population-based-on-the-alternative-identifier&quot;&gt;Retrieving the Population based on the Alternative Identifier&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#parsing-the-email-domain&quot;&gt;Parsing the Email Domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#selecting-a-population-from-a-list&quot;&gt;Selecting a Population from a List&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#changing-the-theme-based-on-the-population&quot;&gt;Changing the Theme based on the Population&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#enforcing-password-policies-based-on-the-population&quot;&gt;Enforcing Password Policies based on the Population&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#enforcing-login-with-an-external-idp-based-on-the-population&quot;&gt;Enforcing Login with an External IdP based on the Population&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#dynamic-registration&quot;&gt;Dynamic Registration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#connecting-the-flow-to-your-applications&quot;&gt;Connecting the Flow to Your Application(s)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#other-helpful-tricks&quot;&gt;Other Helpful Tricks&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#variables&quot;&gt;Variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#parsing-objects-from-a-teleport&quot;&gt;Parsing Objects from a Teleport&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the end of this How-To you’ll have a DaVinci flow that gathers Population metadata based on the User and Population context and applies that metadata during the User’s login and registration experience. Along the way, you’ll learn a wide variety of DaVinci capabilities such as (but not limited to):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Navigating the DaVinci canvas&lt;/li&gt;
&lt;li&gt;Building and using Forms within the page&lt;/li&gt;
&lt;li&gt;Interacting with Connectors, Triggers, and Action Decision Nodes&lt;/li&gt;
&lt;li&gt;Passing data between nodes&lt;/li&gt;
&lt;li&gt;Branching and teleporting during the user’s journey&lt;/li&gt;
&lt;li&gt;Finding and using nodes and flows from the Marketplace&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This How-To expects a beginner level of familiarity with DaVinci and the PingOne console. Since &lt;a href=&quot;https://docs.pingidentity.com/pingone/directory/p1_manage_populations.html&quot;&gt;Managing Populations&lt;/a&gt; is well-defined in the documentation, we won’t be discussing that here.&lt;/p&gt;
&lt;h1&gt;Initial Setup&lt;/h1&gt;
&lt;p&gt;This How-To requires an Environment with PingOne SSO and PingOne DaVinci. Make sure you have the appropriate Environment, licenses, and permissions before continuing.&lt;/p&gt;
&lt;p&gt;You can use the Default Population for this How-To, or you can create a new one: just make sure you have at least one User in that Population so that you can test your flows. The name of my Population is “Example” and my user is “Example User” with the username “&lt;a href=&quot;mailto:example@bxretail.org&quot;&gt;example@bxretail.org&lt;/a&gt;”.&lt;/p&gt;
&lt;p&gt;Fortunately for us, importing and exporting experiences like the one in this document is trivial in PingOne. If you’d like to start with the pre-built example, download the DaVinci flow using the link &lt;a href=&quot;https://github.com/gwizdala/lib-ping/blob/main/How-Tos/davinci-primer-dynamic-populations/dynamic_experiences_with_populations.json&quot;&gt;here&lt;/a&gt; and then import into your Environment to get going.&lt;/p&gt;
&lt;p&gt;That being said, if you’re looking to get a better understanding of &lt;em&gt;how&lt;/em&gt; to construct flows like this, it’s worth the time to follow along with this How-To.&lt;/p&gt;
&lt;h2&gt;Creating the Forms&lt;/h2&gt;
&lt;p&gt;Since we are going to be capturing some user information to log in and are going to be branding them based on our Population, we should create some &lt;a href=&quot;https://docs.pingidentity.com/pingone/user_experience/p1_forms.html&quot;&gt;Forms&lt;/a&gt;. Forms provide a drag-and-drop editor for building user interfaces that we can then reuse throughout our DaVinci Flows.&lt;/p&gt;
&lt;p&gt;Go to “User Experience” - “Forms” in your PingOne navigation bar. You’ll see a series of example forms that can be used in your flows - feel free to click each of them to see what capabilities they provide in the preview.&lt;/p&gt;
&lt;p&gt;Our experience is going to collect the user’s username and then use that information to figure out what Population they are associated with either for registration or authentication. We can quickly create that form by duplicating the &lt;strong&gt;Example - Sign On&lt;/strong&gt; form already made for us.&lt;/p&gt;
&lt;p&gt;Select the form entitled &lt;strong&gt;Example - Sign On&lt;/strong&gt;, click on the three-dot menu (fun fact: those are called &lt;a href=&quot;https://van-ons.nl/en/blog/functioneel-ontwerp/wat-hamburgers-kebab-en-ux-met-elkaar-te-maken-hebben/&quot;&gt;Kebab Menus&lt;/a&gt;) and select &lt;strong&gt;Duplicate&lt;/strong&gt;. You’ll see a new version created called something like &lt;strong&gt;Example - SignOn1&lt;/strong&gt; - select it, hit the three dots and this time click &lt;strong&gt;Edit&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the duplicated form and the highlighted &amp;quot;Edit&amp;quot; menu item&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;811&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/form-edit.D3LjGND1_Z28C2Db.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/form-edit.D3LjGND1_2ldeY2.webp 640w, https://developer.pingidentity.com/blog/_astro/form-edit.D3LjGND1_Z1gWu5x.webp 750w, https://developer.pingidentity.com/blog/_astro/form-edit.D3LjGND1_2k8ybD.webp 828w, https://developer.pingidentity.com/blog/_astro/form-edit.D3LjGND1_4hGlk.webp 1080w, https://developer.pingidentity.com/blog/_astro/form-edit.D3LjGND1_ZI0Ld6.webp 1280w, https://developer.pingidentity.com/blog/_astro/form-edit.D3LjGND1_Z1GE2Uy.webp 1668w, https://developer.pingidentity.com/blog/_astro/form-edit.D3LjGND1_Z28C2Db.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Creating Your First Form&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The Editor view gives us access to a bunch of different capabilities, but we’re not going to need to make a whole lot of changes for our use case.&lt;/p&gt;
&lt;p&gt;First, under Properties (in the left bar) change the Form Name to &lt;strong&gt;Email-First Authentication&lt;/strong&gt;. Then, select the Password field and click on the trash can icon in the left window to delete it. Optionally, select the Username field, change the validation type to “Custom”, the Regex to &lt;code&gt;^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$&lt;/code&gt; and the Error Message to the translation key &lt;code&gt;forms.fields.user.email.errorMessage&lt;/code&gt; (which is the phrase “Invalid Email Address”).&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the form editor for the Email-First Authentication flow. Just the username field is displayed.&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1306&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/form-editor.BgxI8yTP_Z1DETHP.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/form-editor.BgxI8yTP_24cfbE.webp 640w, https://developer.pingidentity.com/blog/_astro/form-editor.BgxI8yTP_247IuB.webp 750w, https://developer.pingidentity.com/blog/_astro/form-editor.BgxI8yTP_Z1nHcdB.webp 828w, https://developer.pingidentity.com/blog/_astro/form-editor.BgxI8yTP_Z1vKLz3.webp 1080w, https://developer.pingidentity.com/blog/_astro/form-editor.BgxI8yTP_24O2yt.webp 1280w, https://developer.pingidentity.com/blog/_astro/form-editor.BgxI8yTP_Z2p3Wrh.webp 1668w, https://developer.pingidentity.com/blog/_astro/form-editor.BgxI8yTP_Z1DETHP.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;The Email-First Authentication Form&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When you’re done, make sure to save the form by hitting the “Save” button in the top-right of the page.&lt;/p&gt;
&lt;h2&gt;Creating Your DaVinci Flow&lt;/h2&gt;
&lt;p&gt;We’re going to be retrieving and responding to our Users’ inputs within a DaVinci flow.&lt;/p&gt;
&lt;p&gt;On your left navbar, click on the DaVinci button. If it’s not there, make sure you’re in an Environment with DaVinci enabled.&lt;/p&gt;
&lt;p&gt;A new tab will open in your browser to DaVinci. Click on the “Flows” button on the left navigation bar and the “Add Flow” in the top right corner of the page. If you’re using the example flow, feel free to select “Import Flow” otherwise click the “Blank Flow” option from the dropdown, giving it the name &lt;strong&gt;Dynamic Experiences with Populations&lt;/strong&gt;. Once you hit “Create”, you’ll be taken into the DaVinci editor, which is where we’ll spend the majority of this How-To.&lt;/p&gt;
&lt;h1&gt;Retrieving the Population&lt;/h1&gt;
&lt;p&gt;Our Population contains important information about our user - things like their password policy, their branding, and if they log in using an external identity provider. To use that information accordingly we need a way to figure out what population our user (or new user) is related to.&lt;/p&gt;
&lt;h2&gt;Retrieving the Population based on the User&lt;/h2&gt;
&lt;h3&gt;Collecting the Username&lt;/h3&gt;
&lt;p&gt;First off, let’s pull Population data based on the User that we’ve identified in our DaVinci flow. Think of this use case as the initial login page for your customers into your business: based on who we see, we’ll dynamically change their experience accordingly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; Connectors, Capabilities, and Triggers&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To start, click the “+” button in the bottom-left corner of your Canvas (that’s the editor window), click “User Interface”, and click on your “Email-First Authentication” form. A little node will snap to your mouse which you can then click on the canvas to add.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the add (+) button that, when clicked, shows a submenu where &amp;quot;User Interface&amp;quot; is selected&quot; loading=&quot;lazy&quot; width=&quot;266&quot; height=&quot;392&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/add-interface.DYJUjkGX_Zl2hC1.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/add-interface.DYJUjkGX_Zl2hC1.webp 266w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A view of the Add User Interface menu which includes the created forms&quot; loading=&quot;lazy&quot; width=&quot;1388&quot; height=&quot;1274&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/add-interface-menu.dTd0GWY__1Kr9Yg.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/add-interface-menu.dTd0GWY__Z1dmDyO.webp 640w, https://developer.pingidentity.com/blog/_astro/add-interface-menu.dTd0GWY__Q2Ged.webp 750w, https://developer.pingidentity.com/blog/_astro/add-interface-menu.dTd0GWY__ZVbcE0.webp 828w, https://developer.pingidentity.com/blog/_astro/add-interface-menu.dTd0GWY__C5LPW.webp 1080w, https://developer.pingidentity.com/blog/_astro/add-interface-menu.dTd0GWY__ZIiyWg.webp 1280w, https://developer.pingidentity.com/blog/_astro/add-interface-menu.dTd0GWY__1Kr9Yg.webp 1388w&quot; /&gt;&amp;gt;
&lt;em&gt;Adding a New Form&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The form is a Connector and is using the “Show Form” capability - you can check this out by clicking the back error at the top of the form editor screen. You’ll also see that you can select the forms you created previously from the “Form” dropdown that appears.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the Form connector in which the &amp;quot;Show Form&amp;quot; trigger has been selected&quot; loading=&quot;lazy&quot; width=&quot;1382&quot; height=&quot;770&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/trigger-show-form.2Wd85ViW_Z1pK23K.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/trigger-show-form.2Wd85ViW_ZBubDJ.webp 640w, https://developer.pingidentity.com/blog/_astro/trigger-show-form.2Wd85ViW_Z1a9S5N.webp 750w, https://developer.pingidentity.com/blog/_astro/trigger-show-form.2Wd85ViW_ZIqgFN.webp 828w, https://developer.pingidentity.com/blog/_astro/trigger-show-form.2Wd85ViW_Z1efsRJ.webp 1080w, https://developer.pingidentity.com/blog/_astro/trigger-show-form.2Wd85ViW_1wBpvF.webp 1280w, https://developer.pingidentity.com/blog/_astro/trigger-show-form.2Wd85ViW_Z1pK23K.webp 1382w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the details within the Show Form Trigger in which the Email-First Authentication form has been selected&quot; loading=&quot;lazy&quot; width=&quot;1346&quot; height=&quot;1076&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/trigger-show-form-details.CtDjd3LF_Z8645x.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/trigger-show-form-details.CtDjd3LF_WOhix.webp 640w, https://developer.pingidentity.com/blog/_astro/trigger-show-form-details.CtDjd3LF_1el2Pr.webp 750w, https://developer.pingidentity.com/blog/_astro/trigger-show-form-details.CtDjd3LF_Z1zSOHh.webp 828w, https://developer.pingidentity.com/blog/_astro/trigger-show-form-details.CtDjd3LF_dfTXG.webp 1080w, https://developer.pingidentity.com/blog/_astro/trigger-show-form-details.CtDjd3LF_Z24wyGn.webp 1280w, https://developer.pingidentity.com/blog/_astro/trigger-show-form-details.CtDjd3LF_Z8645x.webp 1346w&quot; /&gt;&amp;gt;
&lt;em&gt;Selecting the “Show Form” Capability&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At this point you have a single node that collects the username and stores it under the key &lt;code&gt;user.username&lt;/code&gt;. To test, hit the “Deploy” button in the top right corner and then the “Try Flow” button to open up the form in a new tab.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the rendered flow in which the username is presented as an input&quot; loading=&quot;lazy&quot; width=&quot;926&quot; height=&quot;938&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-username.DZRLKDIZ_2h5FfH.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-username.DZRLKDIZ_2fXp1S.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-username.DZRLKDIZ_Z1EPYVM.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-username.DZRLKDIZ_tSmKJ.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-username.DZRLKDIZ_2h5FfH.webp 926w&quot; /&gt;&amp;gt;
&lt;em&gt;A rendered Form&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Nice! In one node you have a log in form. Now, let’s figure out what Population this user is from based on their username.&lt;/p&gt;
&lt;h3&gt;Finding the User&lt;/h3&gt;
&lt;p&gt;Click and drag from the black dot on the right side of the Form connector - a little line should show up. When releasing that line, you’ll be prompted to add a new connector. This time, select the PingOne connector.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the DaVinci canvas where a line has been dragged from the Form node and a selection box has appeared to add a connector. PingOne has been searched and its connector is highlighted&quot; loading=&quot;lazy&quot; width=&quot;1232&quot; height=&quot;1100&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/adding-a-connector.Boj9doBl_Z1AW6Rg.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/adding-a-connector.Boj9doBl_RWVT8.webp 640w, https://developer.pingidentity.com/blog/_astro/adding-a-connector.Boj9doBl_GSoFS.webp 750w, https://developer.pingidentity.com/blog/_astro/adding-a-connector.Boj9doBl_ZVQXtK.webp 828w, https://developer.pingidentity.com/blog/_astro/adding-a-connector.Boj9doBl_ZPwrk2.webp 1080w, https://developer.pingidentity.com/blog/_astro/adding-a-connector.Boj9doBl_Z1AW6Rg.webp 1232w&quot; /&gt;&amp;gt;
&lt;em&gt;Connecting Connectors&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You’ll see that your Form is now connected to a new node with a little grey bubble and a blue line with the word “True” on it. Those bubbles are called &lt;strong&gt;Action Decision Nodes&lt;/strong&gt; and they allow us to branch decisioning logic based on the results of the prior connectors. Right now, that blue “True” is saying that if &lt;strong&gt;All Triggers are True&lt;/strong&gt;, then the Form will continue to the PingOne connector. You can have multiple responses come from the same action decision node, which we’ll use later when handling actions like a user not existing in our Environment. If you ever need to change a trigger, you can right-click the word (in that case, “True”) or click on the Action Decision Node directly.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the Action Decision Node editor in which the All Triggers True condition points to the PingOne node&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;670&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/action-decision.DEwi_b7j_Z1SN38D.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/action-decision.DEwi_b7j_ZhEv1g.webp 640w, https://developer.pingidentity.com/blog/_astro/action-decision.DEwi_b7j_1FWnDp.webp 750w, https://developer.pingidentity.com/blog/_astro/action-decision.DEwi_b7j_Z1aKxw.webp 828w, https://developer.pingidentity.com/blog/_astro/action-decision.DEwi_b7j_akcOF.webp 1080w, https://developer.pingidentity.com/blog/_astro/action-decision.DEwi_b7j_Z2g0Ykr.webp 1280w, https://developer.pingidentity.com/blog/_astro/action-decision.DEwi_b7j_1h9iIK.webp 1668w, https://developer.pingidentity.com/blog/_astro/action-decision.DEwi_b7j_Z1SN38D.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Action Decision Nodes&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now that these two connectors are connected, let’s configure the PingOne node to lookup the user. Click on the PingOne connector and select the “Find User” capability.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; Passing Data with Handlebars&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We are going to be looking up our user based on their username, using the username inputted by the form. To do so, under the &lt;strong&gt;PingOne Attributes&lt;/strong&gt; field type the word &lt;code&gt;username&lt;/code&gt; and hit enter. Then under the identifier, select the “{}” icon in the right side of the input and click on your Form. Any time you see the “{}” icon means that you have access to data collected along the course of your DaVinci flow.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the variables selection screen in which Form is highlighted&quot; loading=&quot;lazy&quot; width=&quot;1298&quot; height=&quot;686&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/handlebars-find.BaxT_eXK_Z1XU84c.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/handlebars-find.BaxT_eXK_1B8nt2.webp 640w, https://developer.pingidentity.com/blog/_astro/handlebars-find.BaxT_eXK_F06z6.webp 750w, https://developer.pingidentity.com/blog/_astro/handlebars-find.BaxT_eXK_1G35U4.webp 828w, https://developer.pingidentity.com/blog/_astro/handlebars-find.BaxT_eXK_57HLc.webp 1080w, https://developer.pingidentity.com/blog/_astro/handlebars-find.BaxT_eXK_Z3Pa2U.webp 1280w, https://developer.pingidentity.com/blog/_astro/handlebars-find.BaxT_eXK_Z1XU84c.webp 1298w&quot; /&gt;&amp;gt;
&lt;em&gt;Collecting Input Data&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once you’ve clicked on your Form connector, you’ll see that the output values include the username keyed based on the defined key we set in the Form Connector. Go ahead and select &lt;code&gt;user.username&lt;/code&gt; as the Identifier.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the user.username variable value being added into the Identifier field via handlebars&quot; loading=&quot;lazy&quot; width=&quot;1292&quot; height=&quot;668&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/handlebars-add.CKhBXogg_1esvvK.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/handlebars-add.CKhBXogg_ZbYAMN.webp 640w, https://developer.pingidentity.com/blog/_astro/handlebars-add.CKhBXogg_Z1f2sPh.webp 750w, https://developer.pingidentity.com/blog/_astro/handlebars-add.CKhBXogg_WlQAu.webp 828w, https://developer.pingidentity.com/blog/_astro/handlebars-add.CKhBXogg_Z1utV9O.webp 1080w, https://developer.pingidentity.com/blog/_astro/handlebars-add.CKhBXogg_Z70mLY.webp 1280w, https://developer.pingidentity.com/blog/_astro/handlebars-add.CKhBXogg_1esvvK.webp 1292w&quot; /&gt;&amp;gt;
&lt;em&gt;Selecting the specific value&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you’re curious why the icons for these values are curly braces, it’s because the data is actually being referenced using &lt;a href=&quot;https://handlebarsjs.com/&quot;&gt;handlebars notation&lt;/a&gt; - if you hover on the value you selected you can see what the reference looks like. For the most part, you’ll probably not have to work with the handlebars directly, but it’s useful to know in cases that you want to ensure a specific value is being used in connectors downstream.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the hovered-over handlebars value on the username field just added&quot; loading=&quot;lazy&quot; width=&quot;1268&quot; height=&quot;206&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/handlebars-view.BmT1FA6Q_5ygoH.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/handlebars-view.BmT1FA6Q_dHjJI.webp 640w, https://developer.pingidentity.com/blog/_astro/handlebars-view.BmT1FA6Q_ZLYHLT.webp 750w, https://developer.pingidentity.com/blog/_astro/handlebars-view.BmT1FA6Q_PHtdq.webp 828w, https://developer.pingidentity.com/blog/_astro/handlebars-view.BmT1FA6Q_1Svwcz.webp 1080w, https://developer.pingidentity.com/blog/_astro/handlebars-view.BmT1FA6Q_5ygoH.webp 1268w&quot; /&gt;&amp;gt;
&lt;em&gt;Seeing the Handlebars representation of the data&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Tip:&lt;/strong&gt; Flows can get quite large quite fast. Consider putting unique titles and descriptions on each connector (you can find that in the “Settings” tab within the action’s editor view) as well as annotations (right-click on the canvas and select “Annotation”) to help you reference the correct values in inputs as well as better understand what a flow is doing when maintaining/editing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hit “Apply” to save your changes.&lt;/p&gt;
&lt;h3&gt;Retrieving the Population Details&lt;/h3&gt;
&lt;p&gt;If a User is found, we know their Population.&lt;/p&gt;
&lt;p&gt;From the “Find User” connector, add an “All Triggers True” action connection to an Http connector with the capability “Custom HTML Message”. The message should say “Your population ID is” with the added variable of the matchedUser.population.id.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A HTTP message node that contains the Population ID gathered from the PingOne node&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1148&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/debug-popid.G27CXOC0_ZighV9.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/debug-popid.G27CXOC0_ZXk5SQ.webp 640w, https://developer.pingidentity.com/blog/_astro/debug-popid.G27CXOC0_ZYbHdy.webp 750w, https://developer.pingidentity.com/blog/_astro/debug-popid.G27CXOC0_ZQRtQu.webp 828w, https://developer.pingidentity.com/blog/_astro/debug-popid.G27CXOC0_ZirO4X.webp 1080w, https://developer.pingidentity.com/blog/_astro/debug-popid.G27CXOC0_1RWz5j.webp 1280w, https://developer.pingidentity.com/blog/_astro/debug-popid.G27CXOC0_20HAl8.webp 1668w, https://developer.pingidentity.com/blog/_astro/debug-popid.G27CXOC0_ZighV9.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Displaying the Population ID&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Apply, Deploy, and then Try your new flow. If you use a pre-existing user, you should see their Population ID.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The resulting Flow display message of the collected Population ID. It is an information card with the text &amp;quot;Your Population ID is&amp;quot; along with a UUID&quot; loading=&quot;lazy&quot; width=&quot;1052&quot; height=&quot;848&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/debug-popid-output.-vPZ7GRd_ZOCAzG.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/debug-popid-output.-vPZ7GRd_2oW0Vm.webp 640w, https://developer.pingidentity.com/blog/_astro/debug-popid-output.-vPZ7GRd_1uPM5R.webp 750w, https://developer.pingidentity.com/blog/_astro/debug-popid-output.-vPZ7GRd_ZxcjAI.webp 828w, https://developer.pingidentity.com/blog/_astro/debug-popid-output.-vPZ7GRd_ZOCAzG.webp 1052w&quot; /&gt;&amp;gt;
&lt;em&gt;The matched user’s Population ID&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A Population ID isn’t too useful by itself. Let’s pull data on the &lt;strong&gt;Population&lt;/strong&gt; directly using the ID we’ve retrieved.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; Disabling Nodes&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Move the Display Population ID connector out of the way, right click, and &lt;strong&gt;Disable&lt;/strong&gt; the connector. This lets us keep the connector in our editor if we want to use it later without it being considered by DaVinci as a valid path. The node will turn semi-transparent to indicate that it’s disabled.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The right-click menu that appears when interacting with a node. The Option to &amp;quot;Disable&amp;quot; is highlighted&quot; loading=&quot;lazy&quot; width=&quot;1004&quot; height=&quot;992&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/disable-node.BjTkFQud_fMeaT.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/disable-node.BjTkFQud_Z1s0qGX.webp 640w, https://developer.pingidentity.com/blog/_astro/disable-node.BjTkFQud_Z1d33PS.webp 750w, https://developer.pingidentity.com/blog/_astro/disable-node.BjTkFQud_Z1faBt6.webp 828w, https://developer.pingidentity.com/blog/_astro/disable-node.BjTkFQud_fMeaT.webp 1004w&quot; /&gt;&amp;gt;
&lt;em&gt;Disabling a Node&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;From the same Action Decision Node, connect a PingOne Connector with the capability “Read Population”. In your input, add the same population ID from your matched user that you used in the Display Population step.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A connected PingOne node in which the Population ID gathered from the prior node is being used to read full population details&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1013&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/read-pop.ChO0O1sA_z350T.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/read-pop.ChO0O1sA_Z1Xfp3T.webp 640w, https://developer.pingidentity.com/blog/_astro/read-pop.ChO0O1sA_ZIKAIS.webp 750w, https://developer.pingidentity.com/blog/_astro/read-pop.ChO0O1sA_Z1XtH1q.webp 828w, https://developer.pingidentity.com/blog/_astro/read-pop.ChO0O1sA_rzJJk.webp 1080w, https://developer.pingidentity.com/blog/_astro/read-pop.ChO0O1sA_Z2dqFHm.webp 1280w, https://developer.pingidentity.com/blog/_astro/read-pop.ChO0O1sA_AVacS.webp 1668w, https://developer.pingidentity.com/blog/_astro/read-pop.ChO0O1sA_z350T.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Reading a Population&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now, if the Population can be found, we can pull more metadata from that Population. Connect a new Http connector with the “Custom HTML Message” capability, toggle on “Show Continue Button”, and add the Message “Your Population data:” with the additional value of the &lt;code&gt;population&lt;/code&gt; output that came from the Read Population node. If you want to add an object or array, rather than a single piece of data, into an input click the “+” button next to the specific object value.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A message node in which the entire population object is being added for display. A red arrow points to the &amp;quot;+&amp;quot; button next to the &amp;quot;population (object)&amp;quot; element in the variables selector&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1102&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/debug-popdata.1LtJOPjS_ZtEnLj.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/debug-popdata.1LtJOPjS_Z2f4U5F.webp 640w, https://developer.pingidentity.com/blog/_astro/debug-popdata.1LtJOPjS_2dcXuW.webp 750w, https://developer.pingidentity.com/blog/_astro/debug-popdata.1LtJOPjS_Z2i7Y17.webp 828w, https://developer.pingidentity.com/blog/_astro/debug-popdata.1LtJOPjS_OtGcd.webp 1080w, https://developer.pingidentity.com/blog/_astro/debug-popdata.1LtJOPjS_2rd6VH.webp 1280w, https://developer.pingidentity.com/blog/_astro/debug-popdata.1LtJOPjS_1nE0vh.webp 1668w, https://developer.pingidentity.com/blog/_astro/debug-popdata.1LtJOPjS_ZtEnLj.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Adding an Object as an Input Value&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Apply, Deploy, and Try your flow. This time, you should see all of the metadata that came back from your user’s population.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;An infomration message display that shows the returned population data object that was collected by the read population node&quot; loading=&quot;lazy&quot; width=&quot;1028&quot; height=&quot;956&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/debug-popdata-output.BtZjbCN7_Z1tiHvt.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/debug-popdata-output.BtZjbCN7_13Fw3v.webp 640w, https://developer.pingidentity.com/blog/_astro/debug-popdata-output.BtZjbCN7_1A3YbH.webp 750w, https://developer.pingidentity.com/blog/_astro/debug-popdata-output.BtZjbCN7_Z1VYHj6.webp 828w, https://developer.pingidentity.com/blog/_astro/debug-popdata-output.BtZjbCN7_Z1tiHvt.webp 1028w&quot; /&gt;&amp;gt;
&lt;em&gt;The User’s Population&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Retrieving the Population based on the Alternative Identifier&lt;/h2&gt;
&lt;p&gt;Next, let’s get the Population based on an Alternative Identifier we’ve been provided during the flow. Alternative Identifiers are custom values that you can set on a Population and are used to help you identify what Population should be selected during a flow. Think of this as a domain (possibly in a redirect URL or in the user’s email address), an ID (in a query parameter or body of a REST call), or any other value you’d like to use to help you recognize what Population matches. This is a great approach when you want specific users to be registered into particular populations as you can identify them even before they have created an account in PingOne.&lt;/p&gt;
&lt;p&gt;In fact, registering by context is exactly what we want to add into our flow. Currently, if you enter in a user that doesn’t exist in PingOne, the flow returns an error. Instead, let’s try to find the right population for this user: falling back to our Default population if nothing matches.&lt;/p&gt;
&lt;h3&gt;Parsing the Email Domain&lt;/h3&gt;
&lt;p&gt;We are going to attempt to match the email domain extracted from our user’s username to an &lt;strong&gt;Alternate Identifier&lt;/strong&gt; within our Population.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; Ping Marketplace&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You probably noticed that when you got the user information, the domain isn’t extracted by default. If you wanted, you could always write a custom function in a Functions Connector to do this for you: but rather than build from scratch, Ping provides a &lt;a href=&quot;https://marketplace.pingone.com/home&quot;&gt;Marketplace&lt;/a&gt; of Nodes and Flows pre-configured to handle the heavy-lifting for you. In this case, let’s use the &lt;a href=&quot;https://marketplace.pingone.com/item/parse-domain-from-email-address&quot;&gt;Parse Domain from Email Address node&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Log into the Marketplace with your PingOne admin credentials, hit the “Copy to clipboard” button in the top right corner of the Marketplace listing, and then in your DaVinci canvas right-click an empty area and select “Paste Nodes”. You’ll now have the connector ready to go in your flow.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the canvas in which the user has right-clicked an empty space to display a menue that includes &amp;quot;add annotation&amp;quot; and &amp;quot;paste nodes&amp;quot;&quot; loading=&quot;lazy&quot; width=&quot;1490&quot; height=&quot;956&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/paste-node.BVrqX_iB_Z16Wsd5.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/paste-node.BVrqX_iB_2riJBe.webp 640w, https://developer.pingidentity.com/blog/_astro/paste-node.BVrqX_iB_28vWdI.webp 750w, https://developer.pingidentity.com/blog/_astro/paste-node.BVrqX_iB_1xYGd1.webp 828w, https://developer.pingidentity.com/blog/_astro/paste-node.BVrqX_iB_2c7379.webp 1080w, https://developer.pingidentity.com/blog/_astro/paste-node.BVrqX_iB_Z2vgCcy.webp 1280w, https://developer.pingidentity.com/blog/_astro/paste-node.BVrqX_iB_Z16Wsd5.webp 1490w&quot; /&gt;&amp;gt;
&lt;em&gt;Pasting Nodes&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As a rule of thumb: if there’s functionality you want but you can’t find a trigger that does it, a custom page in HTML that you want designed, or a more complicated flow that you don’t know how to start on, go to the Marketplace. It’s likely there’s something there that will either do or help you get to the solution you’re looking for.&lt;/p&gt;
&lt;p&gt;Some notable Marketplace listings include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nodes:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.pingone.com/item/user-registration-node-block&quot;&gt;User Registration Node Group&lt;/a&gt;: A set of nodes that complete a registration process. Useful when learning how to design HTML in DaVinci, action on button presses with the A Equals Multiple B trigger, and interacting with teleports.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Styling:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.pingone.com/item/ping-ux-css&quot;&gt;Ping UX - DaVinci CSS&lt;/a&gt;: A set of CSS files to help you quickly stylize and brand your custom HTML pages, compatible with Bootstrap formatting.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.pingone.com/item/davinci-design-studio&quot;&gt;DaVinci Design Studio&lt;/a&gt;: A Chromium web plugin that lets you quickly create custom themes in CSS that you can add as a custom style to your DaVinci flows. If you’re using custom HTML rather than a Form, and don’t have your own CSS to use, this is a great starting point.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Flows:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.pingone.com/item/davinci-dna-workforce-identity-solution&quot;&gt;DaVinci DNA Workforce Solution&lt;/a&gt; and &lt;a href=&quot;https://marketplace.pingone.com/item/davinci-dna-ciam-solution&quot;&gt;DaVinci DNA CIAM Solution&lt;/a&gt;: A comprehensive set of modular functionality built out in reusable subflows, including PingOne MFA, PingID, PingOne Protect, and PingOne Verify. This is the gold standard for how to design flows and should get you 90% of the way there when building out your own designs&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.pingone.com/item/verified-trust-for-workforce-helpdesk-solution&quot;&gt;Verified Trust for Workforce&lt;/a&gt;: A flow that enables a helpdesk user to confirm someone’s identity before performing sensitive actions. These flows are also built on the DNA methodology.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alright: enough about the Marketplace. Let’s wire up this new node.&lt;/p&gt;
&lt;p&gt;Connect the Action Decision Node off of your Find User node to the Parse Email Domain node, right-click the “True” action connection and switch it to “Any Trigger False”. In other words, we’re telling DaVinci to parse the email domain when the user does not exist in PingOne. Finally, within the Parse Email Domain node set the value of the variable &lt;strong&gt;email&lt;/strong&gt; to the username you collected within the form.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Parse Domain from Email editor in which the username collected is being passed into the email variable&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1069&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/marketplace-email.DVZQJg9u_Z1cw46J.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/marketplace-email.DVZQJg9u_Z1mUQBN.webp 640w, https://developer.pingidentity.com/blog/_astro/marketplace-email.DVZQJg9u_Zz4gFh.webp 750w, https://developer.pingidentity.com/blog/_astro/marketplace-email.DVZQJg9u_czRpM.webp 828w, https://developer.pingidentity.com/blog/_astro/marketplace-email.DVZQJg9u_Z1x5lan.webp 1080w, https://developer.pingidentity.com/blog/_astro/marketplace-email.DVZQJg9u_1sgYcD.webp 1280w, https://developer.pingidentity.com/blog/_astro/marketplace-email.DVZQJg9u_1hxgpg.webp 1668w, https://developer.pingidentity.com/blog/_astro/marketplace-email.DVZQJg9u_Z1cw46J.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Parsing the Email Domain&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Hit “Apply”, and then drag and create a new PingOne Connector off of your Parse Domain node. From the new node select the “Read Population” trigger again but this time change the Population search to “Use Alternative Identifier” and pass in the domain that was parsed in the prior node.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The parsed email domain being passed as a variable into the read population query, used as an alternative identifier&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;898&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/domain-search.Cd2SCLyt_ZwcCtS.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/domain-search.Cd2SCLyt_1T2s53.webp 640w, https://developer.pingidentity.com/blog/_astro/domain-search.Cd2SCLyt_1w4Uoo.webp 750w, https://developer.pingidentity.com/blog/_astro/domain-search.Cd2SCLyt_2tO8cV.webp 828w, https://developer.pingidentity.com/blog/_astro/domain-search.Cd2SCLyt_Z1qjUht.webp 1080w, https://developer.pingidentity.com/blog/_astro/domain-search.Cd2SCLyt_2a2761.webp 1280w, https://developer.pingidentity.com/blog/_astro/domain-search.Cd2SCLyt_k9IcG.webp 1668w, https://developer.pingidentity.com/blog/_astro/domain-search.Cd2SCLyt_ZwcCtS.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Reading the Population from the Alternate Identifier&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now that we have queried Populations based on the identifier, let’s display the resulting selection. Unlike reading the Population based on ID, though, the same alternative identifier could exist in multiple populations - meaning our response could return more than one population!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; Cloning, Copying, and Pasting Nodes&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To see this in action, we’ll display that result in a message like we did before. But rather than build a brand new node, right-click and &lt;strong&gt;Clone&lt;/strong&gt; the existing HTTP node with our population’s message. Cloning a node, or copy/pasting a selection of nodes (highlighted by holding and dragging your mouse), makes building larger DaVinci flows faster and easier.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A zoomed-in screenshot of the Http node in which the &amp;quot;Clone&amp;quot; command is clearly visible - displayed after right-clicking the element&quot; loading=&quot;lazy&quot; width=&quot;752&quot; height=&quot;408&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/clone-node.k-PIMb8n_Z1eahBR.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/clone-node.k-PIMb8n_Z2vHqd5.webp 640w, https://developer.pingidentity.com/blog/_astro/clone-node.k-PIMb8n_ExfWd.webp 750w, https://developer.pingidentity.com/blog/_astro/clone-node.k-PIMb8n_Z1eahBR.webp 752w&quot; /&gt;&amp;gt;
&lt;em&gt;Cloning a Node&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Wire the response of Read Population to the cloned node, and then update your cloned node to look at the &lt;code&gt;populations&lt;/code&gt; response from the query rather than a single population. &lt;strong&gt;An important note - the population value in your cloned node is still pointing to the original connector it was connected to (i.e. Read Population by ID). When cloning, make sure your references are matching the correct nodes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A message node displaying all populations tied to the alternative identifier&quot; loading=&quot;lazy&quot; width=&quot;1646&quot; height=&quot;800&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/debug-popsdata.BO-7LonT_2eY6nJ.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/debug-popsdata.BO-7LonT_ZRk8vI.webp 640w, https://developer.pingidentity.com/blog/_astro/debug-popsdata.BO-7LonT_Z2lFWVS.webp 750w, https://developer.pingidentity.com/blog/_astro/debug-popsdata.BO-7LonT_ZzAzSN.webp 828w, https://developer.pingidentity.com/blog/_astro/debug-popsdata.BO-7LonT_2eM9sd.webp 1080w, https://developer.pingidentity.com/blog/_astro/debug-popsdata.BO-7LonT_ZNUN93.webp 1280w, https://developer.pingidentity.com/blog/_astro/debug-popsdata.BO-7LonT_2eY6nJ.webp 1646w&quot; /&gt;&amp;gt;
&lt;em&gt;Displaying all Populations&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With this all connected, applied, and deployed, add an alternative identifier to a Population that matches an email domain you can test. In this example, I’m using “bxretail.org”.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Population admin view in which a population, Example, has been created with &amp;quot;bxretail.org&amp;quot; as an alternative identifier&quot; loading=&quot;lazy&quot; width=&quot;1802&quot; height=&quot;800&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/pops-altid.zCDVVn3O_4jOyT.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/pops-altid.zCDVVn3O_1PhdQr.webp 640w, https://developer.pingidentity.com/blog/_astro/pops-altid.zCDVVn3O_ZKCt2n.webp 750w, https://developer.pingidentity.com/blog/_astro/pops-altid.zCDVVn3O_2lx8sQ.webp 828w, https://developer.pingidentity.com/blog/_astro/pops-altid.zCDVVn3O_vsCCD.webp 1080w, https://developer.pingidentity.com/blog/_astro/pops-altid.zCDVVn3O_ZzSkBP.webp 1280w, https://developer.pingidentity.com/blog/_astro/pops-altid.zCDVVn3O_ZoTgbK.webp 1668w, https://developer.pingidentity.com/blog/_astro/pops-altid.zCDVVn3O_4jOyT.webp 1802w&quot; /&gt;&amp;gt;
&lt;em&gt;Setting the Alternative Identifier on the Population&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now, when I try the flow and enter a user who doesn’t exist using that email domain, the population (or populations) with that alternative identifier show up.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The rendered DaVinci flow in which a new user using the matched email address is being entered&quot; loading=&quot;lazy&quot; width=&quot;1020&quot; height=&quot;1018&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/debug-popsdata-entry.oB_5j4m0_14AY2.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/debug-popsdata-entry.oB_5j4m0_Z1NzvwY.webp 640w, https://developer.pingidentity.com/blog/_astro/debug-popsdata-entry.oB_5j4m0_Z1AfkVH.webp 750w, https://developer.pingidentity.com/blog/_astro/debug-popsdata-entry.oB_5j4m0_1GzcvD.webp 828w, https://developer.pingidentity.com/blog/_astro/debug-popsdata-entry.oB_5j4m0_14AY2.webp 1020w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;The resulting population(s) data displaying to the user in the DaVinci flow&quot; loading=&quot;lazy&quot; width=&quot;1056&quot; height=&quot;964&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/debug-popsdata-output.DmMJFhFh_Z1uiCMj.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/debug-popsdata-output.DmMJFhFh_1UfJiB.webp 640w, https://developer.pingidentity.com/blog/_astro/debug-popsdata-output.DmMJFhFh_1egLJD.webp 750w, https://developer.pingidentity.com/blog/_astro/debug-popsdata-output.DmMJFhFh_Z1RSJC4.webp 828w, https://developer.pingidentity.com/blog/_astro/debug-popsdata-output.DmMJFhFh_Z1uiCMj.webp 1056w&quot; /&gt;&amp;gt;
&lt;em&gt;Retrieving Populations on Email Domain&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Selecting a Population from a List&lt;/h3&gt;
&lt;p&gt;When the User does not exist and we query Populations based on the email domain, there are a series of outcomes we need to account for:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The email domain is associated to one Population&lt;/li&gt;
&lt;li&gt;The email domain is associated to more than one Population - in which we let the user select their Population&lt;/li&gt;
&lt;li&gt;The email domain isn’t associated with any Population - in which we fall back to the Default Population&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To do so, we will implement some &lt;strong&gt;Function Connectors&lt;/strong&gt; to branch our path. Function Connectors let us compare values and make decisions based on what we find, or run JavaScript to do things like modify or create values to pass into downstream connectors.&lt;/p&gt;
&lt;p&gt;Right-click and &lt;strong&gt;Delete&lt;/strong&gt; your HTTP node that displays populations. We won’t be needing it anymore.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; Functions Connectors**&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In its place connect a “Functions” Connector and select the “A &amp;gt; B” Trigger, adding the title “Any Pops Found?”. Set the &lt;strong&gt;Type&lt;/strong&gt; to “Number”.&lt;/p&gt;
&lt;p&gt;In &lt;strong&gt;A&lt;/strong&gt;, add the “rawResponse” field from your Read Populations node, hover over the field, and copy the handlebars reference that appears. It’ll look something like &lt;code&gt;{{local.68dcy1mtem.payload.output.rawResponse}}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A hover-over view of the raw response variable for the PingOne connector with the handlebars value highlighted&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;707&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/variable-copy.DiD_t93F_ZUKTwj.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/variable-copy.DiD_t93F_Z7748W.webp 640w, https://developer.pingidentity.com/blog/_astro/variable-copy.DiD_t93F_Zgdw82.webp 750w, https://developer.pingidentity.com/blog/_astro/variable-copy.DiD_t93F_ZMM7LU.webp 828w, https://developer.pingidentity.com/blog/_astro/variable-copy.DiD_t93F_ZYHUJu.webp 1080w, https://developer.pingidentity.com/blog/_astro/variable-copy.DiD_t93F_2iDS6y.webp 1280w, https://developer.pingidentity.com/blog/_astro/variable-copy.DiD_t93F_Z2oT84O.webp 1668w, https://developer.pingidentity.com/blog/_astro/variable-copy.DiD_t93F_ZUKTwj.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Getting the Raw Response&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The triggers within the PingOne connector are making API calls behind the scenes, and if you know the &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/platform/populations/read-all-populations.html&quot;&gt;Read Populations&lt;/a&gt; request you’re aware that there’s an additional field that’s hidden in this result set: the &lt;strong&gt;size&lt;/strong&gt; - which informs you how many results have been returned from the request.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; Accessing Hidden Fields&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Just because the field doesn’t show up in the schema selector doesn’t mean that the field is missing - it just means that the connector didn’t define that value in its output schema. To get access to size, modify your copied handlebars to include size as a key, something like &lt;code&gt;{{local.68dcy1mtem.payload.output.rawResponse.size}}&lt;/code&gt; and then copy/paste that change as Value A in your A &amp;gt; B node. Value B, then, should be set to &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The A &amp;gt; B connector in which the hidden size variable has been added into the A field to be compared against B&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1032&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/variable-hidden.BPCrfAO4_Z1cNgpv.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/variable-hidden.BPCrfAO4_1rk7yn.webp 640w, https://developer.pingidentity.com/blog/_astro/variable-hidden.BPCrfAO4_tn6vl.webp 750w, https://developer.pingidentity.com/blog/_astro/variable-hidden.BPCrfAO4_Z1PszBn.webp 828w, https://developer.pingidentity.com/blog/_astro/variable-hidden.BPCrfAO4_2ll7Ga.webp 1080w, https://developer.pingidentity.com/blog/_astro/variable-hidden.BPCrfAO4_ZOuJjw.webp 1280w, https://developer.pingidentity.com/blog/_astro/variable-hidden.BPCrfAO4_tzq6k.webp 1668w, https://developer.pingidentity.com/blog/_astro/variable-hidden.BPCrfAO4_Z1cNgpv.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Accessing Hidden Fields&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Tip:&lt;/strong&gt; That seemingly arbitrary string of values after &lt;code&gt;local&lt;/code&gt; is your Node ID - and it’s how each node is being referenced throughout your flow. You can find the Node ID under the header of the Node editor or by toggling “Show Node IDs” in your flow settings (top-right menu).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We now have a True/False branch we can use. Connect an “All Triggers True” response to the Function Connector with the Trigger “A &amp;gt; B”, the &lt;strong&gt;Title&lt;/strong&gt; “More than One Pop Found?”, &lt;strong&gt;A&lt;/strong&gt; set to the handlebars you used in the prior node, &lt;strong&gt;B&lt;/strong&gt; set to 1, and the &lt;strong&gt;Type&lt;/strong&gt; set to Number.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The A &amp;gt; B node in which the size is being compared against the value 1&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;896&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/check-many-pops.2efS9G6o_XNfYN.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/check-many-pops.2efS9G6o_Z2doi3.webp 640w, https://developer.pingidentity.com/blog/_astro/check-many-pops.2efS9G6o_syVqW.webp 750w, https://developer.pingidentity.com/blog/_astro/check-many-pops.2efS9G6o_1fn4pg.webp 828w, https://developer.pingidentity.com/blog/_astro/check-many-pops.2efS9G6o_Z2j32YE.webp 1080w, https://developer.pingidentity.com/blog/_astro/check-many-pops.2efS9G6o_1W3khX.webp 1280w, https://developer.pingidentity.com/blog/_astro/check-many-pops.2efS9G6o_2hvv0Y.webp 1668w, https://developer.pingidentity.com/blog/_astro/check-many-pops.2efS9G6o_XNfYN.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Checking for Many Populations&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;From that same Action Decision, wire an “Any Trigger False” to a PingOne Node with the Trigger “Read Population” and the selector set to “Default”.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Any Trigger False Branch of the Any Pops Found? node conencting to a Read Population trigger (PingOne Connector)&quot; loading=&quot;lazy&quot; width=&quot;1588&quot; height=&quot;866&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/get-default.CXbAuUiS_Z1QNhoD.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/get-default.CXbAuUiS_Z10qNHn.webp 640w, https://developer.pingidentity.com/blog/_astro/get-default.CXbAuUiS_ZNfxrv.webp 750w, https://developer.pingidentity.com/blog/_astro/get-default.CXbAuUiS_1VAgoz.webp 828w, https://developer.pingidentity.com/blog/_astro/get-default.CXbAuUiS_Z2l2k7R.webp 1080w, https://developer.pingidentity.com/blog/_astro/get-default.CXbAuUiS_Z15334s.webp 1280w, https://developer.pingidentity.com/blog/_astro/get-default.CXbAuUiS_Z1QNhoD.webp 1588w&quot; /&gt;&amp;gt;
&lt;em&gt;Getting the Default Pop When None are Found&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; HTML Templates and Custom CSS/JavaScript&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If more than one Population is found, we want the User to select their preferred Population. To do this, we are going to go back to the marketplace to insert the &lt;a href=&quot;https://marketplace.pingone.com/item/format-select-options-for-custom-html-node-group&quot;&gt;Dynamic Dropdown node&lt;/a&gt;. This node pack is a bit different than our forms as it uses the HTTP Connector’s Custom HTML Template to render content, which gives us a great opportunity to review a more complex node.&lt;/p&gt;
&lt;p&gt;First off, connect an “All Triggers True” trigger from the “More than One Pop Found?” node to the “Form Options Array” node that you just imported. Then, in that node you just connected, add the “populations” array gathered from your “Read Pop(s) by Alt ID” Node, setting the &lt;code&gt;optionLabelKey&lt;/code&gt; to &lt;code&gt;name&lt;/code&gt; and the &lt;code&gt;optionValueKey&lt;/code&gt; to &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Form Options Array function editor in which the populations data has been added with the mapped keys&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;980&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/form-options-array.BYMD7bKf_ZsuaJQ.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/form-options-array.BYMD7bKf_ZQ1CLE.webp 640w, https://developer.pingidentity.com/blog/_astro/form-options-array.BYMD7bKf_1zyfzd.webp 750w, https://developer.pingidentity.com/blog/_astro/form-options-array.BYMD7bKf_Zc1Fey.webp 828w, https://developer.pingidentity.com/blog/_astro/form-options-array.BYMD7bKf_ZnKBON.webp 1080w, https://developer.pingidentity.com/blog/_astro/form-options-array.BYMD7bKf_rbJ9h.webp 1280w, https://developer.pingidentity.com/blog/_astro/form-options-array.BYMD7bKf_Z6TdBF.webp 1668w, https://developer.pingidentity.com/blog/_astro/form-options-array.BYMD7bKf_ZsuaJQ.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Connecting the Populations List&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Create a new Population (I named mine “Example 2”) with the same alternative identifier, and then Apply, Save, Deploy, and Try the Flow. When you enter in a new User with that domain, you should see a dropdown with the associated Populations. But what gives - the page looks terrible!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A header of &amp;quot;select an option&amp;quot; with a dropdown and a continue button. It is raw HTML with no formatting&quot; loading=&quot;lazy&quot; width=&quot;572&quot; height=&quot;224&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/display-dropdown-nocss.CVPyfyyt_1Xkvya.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/display-dropdown-nocss.CVPyfyyt_1Xkvya.webp 572w&quot; /&gt;&amp;gt;
&lt;em&gt;The Unformatted HTML Page&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The HTTP Node you imported lets you serve custom HTML, CSS, and JavaScript - it’s great in cases when you require heavier customization, interactivity, or interaction with an external CMS. In this case, our template that we imported relies on &lt;a href=&quot;https://marketplace.pingone.com/item/ping-ux-css&quot;&gt;Ping UX - DaVinci CSS&lt;/a&gt;: so let’s import that into the flow.&lt;/p&gt;
&lt;p&gt;To do so, click on the menu (three dots) in the top right corner and select “Flow Settings”, and then in the “Customizations” tab under “Page Customization” enable “Use Custom CSS”. There you can add in the Custom CSS Rules and the Custom CSS files provided in the Marketplace (or, for your own use cases, the custom assets you have from your own system).&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The settings window in which the custom css rules and custom css files have been added. Use Custom CSS is turned on&quot; loading=&quot;lazy&quot; width=&quot;1118&quot; height=&quot;992&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/add-css.BCPrJ8_y_Z1Q9JJu.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/add-css.BCPrJ8_y_2pGFl5.webp 640w, https://developer.pingidentity.com/blog/_astro/add-css.BCPrJ8_y_25K0Eh.webp 750w, https://developer.pingidentity.com/blog/_astro/add-css.BCPrJ8_y_10wB0Y.webp 828w, https://developer.pingidentity.com/blog/_astro/add-css.BCPrJ8_y_7t0fB.webp 1080w, https://developer.pingidentity.com/blog/_astro/add-css.BCPrJ8_y_Z1Q9JJu.webp 1118w&quot; /&gt;&amp;gt;
&lt;em&gt;Adding the Custom Ping CSS&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Tip:&lt;/strong&gt; Want a starting off point? Add the &lt;a href=&quot;https://marketplace.pingone.com/item/davinci-design-studio&quot;&gt;DaVinci Design Studio&lt;/a&gt; Chrome extension to get custom css that can be used in tandem with the Ping CSS provided.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Apply, Save, Deploy, and Try the Flow again. You’ll see the form shows up with the formatting matching the baseline branding in your form editor, with one exception: the Logo isn’t showing up.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The same select page but in a nicely-formatted card with the appropriate font and input styling&quot; loading=&quot;lazy&quot; width=&quot;914&quot; height=&quot;572&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/display-dropdown-formatted.CHQzikui_ZOWaVC.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/display-dropdown-formatted.CHQzikui_Z1mRA6q.webp 640w, https://developer.pingidentity.com/blog/_astro/display-dropdown-formatted.CHQzikui_ZLWrfj.webp 750w, https://developer.pingidentity.com/blog/_astro/display-dropdown-formatted.CHQzikui_Z2h1bpX.webp 828w, https://developer.pingidentity.com/blog/_astro/display-dropdown-formatted.CHQzikui_ZOWaVC.webp 914w&quot; /&gt;&amp;gt;
&lt;em&gt;The Properly-Formatted Dropdown&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you read through the Dynamic Dropdown node, you’ll see that it also (optionally) expects the &lt;a href=&quot;https://marketplace.pingone.com/item/davinci-branding-variables-node&quot;&gt;DaVinci Branding Variables Node&lt;/a&gt;. This node lets you quickly and easily set the name of your company and the URL of your logo to be used across all HTTP nodes. Copy that node and place it at the beginning of your flow, connected to the first form in which you show the username.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The first node connected is the Set Company &amp;amp; Logo node connected to the Username form&quot; loading=&quot;lazy&quot; width=&quot;1496&quot; height=&quot;374&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/set-logo.BEmloulR_2a7KLc.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/set-logo.BEmloulR_Z1AuN0p.webp 640w, https://developer.pingidentity.com/blog/_astro/set-logo.BEmloulR_ZwKtQ4.webp 750w, https://developer.pingidentity.com/blog/_astro/set-logo.BEmloulR_KCORi.webp 828w, https://developer.pingidentity.com/blog/_astro/set-logo.BEmloulR_Z5IbLp.webp 1080w, https://developer.pingidentity.com/blog/_astro/set-logo.BEmloulR_1WqYte.webp 1280w, https://developer.pingidentity.com/blog/_astro/set-logo.BEmloulR_2a7KLc.webp 1496w&quot; /&gt;&amp;gt;
&lt;em&gt;Set Company &amp;amp; Logo Node&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You know the drill. Apply, Save, Deploy, Try. Now your logo shows up on the page too.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The formatted dropdown with the Ping Identity Logo displayed&quot; loading=&quot;lazy&quot; width=&quot;872&quot; height=&quot;806&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/display-dropdown-withlogo.vDlxWl_S_Z1dVGBr.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/display-dropdown-withlogo.vDlxWl_S_1hQIdb.webp 640w, https://developer.pingidentity.com/blog/_astro/display-dropdown-withlogo.vDlxWl_S_ZbKnrt.webp 750w, https://developer.pingidentity.com/blog/_astro/display-dropdown-withlogo.vDlxWl_S_ZEWitl.webp 828w, https://developer.pingidentity.com/blog/_astro/display-dropdown-withlogo.vDlxWl_S_Z1dVGBr.webp 872w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; Input and Output Schema&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Both the Functions Connector and the HTTP Connector we just added have an &lt;strong&gt;Input&lt;/strong&gt; and an &lt;strong&gt;Output schema.&lt;/strong&gt; These schemas exist so that you can add specific data requirements on what data is needed for this node to function and what data will be returned for use in downstream nodes.&lt;/p&gt;
&lt;p&gt;Let’s take a look at how the schemas are defined and what they look like in practice, starting with the “Form Options Array” Custom Function Node.&lt;/p&gt;
&lt;p&gt;Clicking into this node, you’ll see that some JavaScript is being executed to parse an array of objects and format that array into something that the HTML node can use when building the dropdown. At the top of the node you’ll see a &lt;strong&gt;Variable Input List&lt;/strong&gt; - this is where the variables that are being passed in the &lt;code&gt;params&lt;/code&gt; object you can see referenced in line 3 of the script are coming from. These variables are given a name, a value, and a data type, which helps inform the script how to parse and interact with that params value.&lt;/p&gt;
&lt;p&gt;Underneath the hood, these variables are being defined with an &lt;strong&gt;Input Schema&lt;/strong&gt; using &lt;a href=&quot;https://json-schema.org/&quot;&gt;JSON Schema&lt;/a&gt; formatting. In the case of the Custom Function Node, how those parameters are passed into the JavaScript is handled for you.&lt;/p&gt;
&lt;p&gt;At the other end of the JavaScript function, you’ll see that you’re returning an object with the key &lt;code&gt;options&lt;/code&gt;). This contains the key/value pairs that are being referenced in the HTML node. But while this data is being returned, the JavaScript by itself doesn’t inform the downstream nodes what the data looks like. The &lt;strong&gt;Output Schema&lt;/strong&gt; provides that definition.&lt;/p&gt;
&lt;p&gt;Scrolling to the bottom of the node definition, you’ll see an Output Schema section with the following JSON:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;output&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;object&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;properties&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;options&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;array&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;items&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;object&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;properties&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;label&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;value&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;required&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;label&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;value&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          ]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll see that the &lt;strong&gt;output&lt;/strong&gt; of this node is expected to return a property of name &lt;strong&gt;options&lt;/strong&gt;, which is an &lt;strong&gt;array that&lt;/strong&gt; contains &lt;strong&gt;objects&lt;/strong&gt; whose properties are &lt;strong&gt;label&lt;/strong&gt; and &lt;strong&gt;value&lt;/strong&gt;: both strings.&lt;/p&gt;
&lt;p&gt;When you click on the “Dynamic Dropdown” HTTP Node, you’ll see that the &lt;strong&gt;options&lt;/strong&gt; array is passed and that you can reference it within the output of the Form Options Array node. Since the Label and Value were also defined, you can see that you can select that option directly from that object, to do things like target a specific element in the array.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The &amp;quot;options&amp;quot; result appearing in a variables selector as defined from the Form Options Array node&quot; loading=&quot;lazy&quot; width=&quot;1370&quot; height=&quot;650&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/output-variables.BGaTWgyy_173AXT.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/output-variables.BGaTWgyy_28655S.webp 640w, https://developer.pingidentity.com/blog/_astro/output-variables.BGaTWgyy_Z1sEs9t.webp 750w, https://developer.pingidentity.com/blog/_astro/output-variables.BGaTWgyy_ZM5HvX.webp 828w, https://developer.pingidentity.com/blog/_astro/output-variables.BGaTWgyy_2jdw5B.webp 1080w, https://developer.pingidentity.com/blog/_astro/output-variables.BGaTWgyy_BIhdI.webp 1280w, https://developer.pingidentity.com/blog/_astro/output-variables.BGaTWgyy_173AXT.webp 1370w&quot; /&gt;&amp;gt;
&lt;em&gt;The Resulting Output&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Since we are in the HTTP node, let’s take a look at how the inputs are formatted - since all of the input elements up to the HTML template are special to just this created node. Instead of an input editor like we saw in the Function Node, the Inputs are defined in an Input Schema halfway down the page - right next to the Output schema and just below the Form validation rules.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Tip:&lt;/strong&gt; If you are making your own HTML template, toggle the “Switch View” button to view the template using a classic code editor. When you’re adding variables, toggle back and use the “{}” button like you’ve done in other inputs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Input Schema here defines not only what values are expected to be available, but also how the inputs should be formatted for the user to fill in (&lt;code&gt;preferredControlType&lt;/code&gt;) and whether or not the user can dynamically pass parameters from other nodes (&lt;code&gt;enableParameters&lt;/code&gt;). The type and displayName indicate how the input is used and how it shows up for the user in this node. When editing the Input Schema, it’s important to know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your inputs should be put within the &lt;code&gt;properties&lt;/code&gt; object.&lt;/li&gt;
&lt;li&gt;The only &lt;code&gt;preferredControlType&lt;/code&gt; available is &lt;code&gt;textField&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Your inputs &lt;strong&gt;must use unique keys&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If you define a propertyName, &lt;strong&gt;the propertyName must match the key.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;It’s best to edit the Input Schema in a separate text editor. It makes editing and validating the schema easier.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try adding another input - let’s call it &lt;code&gt;example&lt;/code&gt;. It should look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;example&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;My New Example Input&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;preferredControlType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;textField&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;enableParameters&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apply your changes. You’ll see the new input ready to go with the key &lt;code&gt;example&lt;/code&gt; available for use in your HTML, CSS, and JavaScript via handlebars.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The new &amp;quot;My New Example Input&amp;quot; input created from the input schema update&quot; loading=&quot;lazy&quot; width=&quot;1298&quot; height=&quot;236&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/input-schema.CXFryIKy_ZbHRtN.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/input-schema.CXFryIKy_2maDqD.webp 640w, https://developer.pingidentity.com/blog/_astro/input-schema.CXFryIKy_hrpAr.webp 750w, https://developer.pingidentity.com/blog/_astro/input-schema.CXFryIKy_Z2jtr02.webp 828w, https://developer.pingidentity.com/blog/_astro/input-schema.CXFryIKy_Z2wXuV7.webp 1080w, https://developer.pingidentity.com/blog/_astro/input-schema.CXFryIKy_Zzb6SL.webp 1280w, https://developer.pingidentity.com/blog/_astro/input-schema.CXFryIKy_ZbHRtN.webp 1298w&quot; /&gt;&amp;gt;
&lt;em&gt;The New Input&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There’s no need to keep that input. You can remove or leave it in, your call.&lt;/p&gt;
&lt;p&gt;The Input and Output Schema shows up throughout DaVinci - you’ll see it in Teleports (we’ll get to that later) and the overall Input Schema for your flow. These definitions allow you to strongly define what is being passed in and out of each node, from one flow to another, or from another application into the DaVinci flow.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; Output Fields&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While looking at the HTTP Node you probably noticed that there was nothing defined in the Output Schema but that node needs to return some data to the next node - in this case, what was selected and what button was pressed. Custom HTML Templates reference output data defined directly in the HTML itself and use the Output Fields list to link that data back to the node’s output.&lt;/p&gt;
&lt;p&gt;In the Output Fields List section you should see two properties: one called &lt;code&gt;select&lt;/code&gt; and one called &lt;code&gt;buttonValue&lt;/code&gt;. Now, take a look in the HTML Template: you’ll see that our select has the &lt;code&gt;id&lt;/code&gt; of &lt;code&gt;select&lt;/code&gt; and our submit button has the &lt;code&gt;data-skbuttontype&lt;/code&gt; of &lt;code&gt;form-submit&lt;/code&gt; and the &lt;code&gt;data-skbuttonvalue&lt;/code&gt; of &lt;code&gt;CONTINUE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For standard inputs, the &lt;code&gt;id&lt;/code&gt; of that input is referenced in the Output Fields List. For buttons, that definition is handled by the &lt;code&gt;sk&lt;/code&gt; attributes that live on it - in particular, that &lt;code&gt;skbuttonValue&lt;/code&gt; is holding the value that you can reference later, and that “buttonValue” output field is referring to it.&lt;/p&gt;
&lt;p&gt;For a list of SK Attributes, head to the documentation &lt;a href=&quot;https://docs.pingidentity.com/davinci/flows/davinci_sk_attributes.html&quot;&gt;here&lt;/a&gt;. For a list of standard components that can be added to HTML (called SK-Components), such as polling, camera, recaptcha, and filepickers, go &lt;a href=&quot;https://docs.pingidentity.com/davinci/flows/davinci_sk_components.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You’ve also probably noticed that both the button and the select have a Data Type of String and a Control Type of Text Field. For all intents and purposes, those two fields should be left as-is: the conversion from your HTML to the other options may not work as expected.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Tip:&lt;/strong&gt; When working with forms, make sure that the &lt;code&gt;id&lt;/code&gt; of your form matches the &lt;code&gt;data-skform&lt;/code&gt; html tag on your submission buttons - otherwise, your DaVinci flow won’t receive the results of your form data.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Changing the Theme based on the Population&lt;/h1&gt;
&lt;p&gt;You now have a means to identify the Population based on an existing User or a new User with the added capability for the User to select their Population if their username matches more than one. Let’s cater the experience based on the context that their Population provides.&lt;/p&gt;
&lt;p&gt;Each Population can be assigned its own Theme. This Theme can then be referenced within DaVinci to stylize your user’s experiences.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; Teleports&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We have two different experiences that we will want to present to our user, a &lt;strong&gt;Login&lt;/strong&gt; experience and a &lt;strong&gt;Registration&lt;/strong&gt; experience, and more than one place in which we need to surface it. In the case of Registration, for instance, we both will need to perform the registration process if a population was selected by the user, if there was only one population found, or if the default population was selected instead. You could duplicate functionality, but if you want to change something later you’re going to have to find and change each implementation one at a time, which could lead to some pretty hard to troubleshoot errors. You could wire up each node to the same path, but not only does that get visually confusing you’ll have to handle all of the different outputs per node. This is where &lt;strong&gt;Teleports&lt;/strong&gt; come in.&lt;/p&gt;
&lt;p&gt;From a coding sense, think of a DaVinci flow as a &lt;strong&gt;module&lt;/strong&gt;. It likely performs a series of things in combination with each other to enact a larger action - like registration and login. Within that module are &lt;strong&gt;functions&lt;/strong&gt; - little bits of smaller atomic actions that are invoked for the module to do its thing. &lt;strong&gt;Teleports&lt;/strong&gt; are these functions. Teleports allow you to break out smaller pieces of functionality into reusable segments that can be called on, looped from, and reused without any copy/pasting or complex wiring.&lt;/p&gt;
&lt;p&gt;As a rule of thumb, if you are reusing the same nodes over and over again, consider putting them in a Teleport.&lt;/p&gt;
&lt;p&gt;To start, scroll below the flow you’ve built so far and add an annotation to indicate what this teleport is for (I’m using “Login”). It’s best practice to write flows horizontally and individual teleports vertically, ideally with the teleports in execution order (for example, if you have a teleport that returns a session or an error, it should probably be at the bottom) - that way, you can read through the content of a flow similar to how you would read a flowchart or diagram.&lt;/p&gt;
&lt;p&gt;Next, add in a Teleport connector with the trigger “Define a Start Node”. Once you do, you’ll see something pretty familiar - an Input schema! Teleport Start Nodes tell the flow where a new section begins and what data can be passed into that section: think of it like a function definition.&lt;/p&gt;
&lt;p&gt;First off, change the name of your Teleport in the Settings tab - you’ll use that name to identify the Teleport later. I’m naming mine “Login”.&lt;/p&gt;
&lt;p&gt;The Input Schema should look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;object&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;properties&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;p1UserId&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;PingOne User ID&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;preferredControlType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;textField&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;enableParameters&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;p1UserId&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;p1UserName&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;PingOne Username&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;preferredControlType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;textField&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;enableParameters&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;p1UserName&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;populationId&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Population ID&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;preferredControlType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;textField&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;enableParameters&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;populationId&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;populationThemeId&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;populationThemeId&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Population Theme ID&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;enableParameters&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;preferredControlType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;textField&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;populationIdPId&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;populationIdPId&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Population IdP ID&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;enableParameters&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;preferredControlType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;textField&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows us to pass our PingOne User’s ID and Population Details into the Teleport.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Tip:&lt;/strong&gt; Teleports stringify the data that comes into it and as a result, won’t be able to handle nested object definitions. Either define your required fields individually in the input schema, set them in a Flow Instance Variable, or write a custom function to parse the values from the stringified object.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In our case, since we only need two values from our Population it’s easiest to define them at the top level.&lt;/p&gt;
&lt;p&gt;Now connect a Form Connector with the Show Form Trigger to your Teleport Start Node, selecting the “Password Authentication” form. Change the Form Theme to “Use Theme ID” and pass in the Theme ID from your parsed Population.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Show Form Editor window in which the Population Theme ID collected from the teleport has been added as a variable&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1266&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/teleport-inputs.CeJBMzcb_ZEowoj.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/teleport-inputs.CeJBMzcb_1tX99k.webp 640w, https://developer.pingidentity.com/blog/_astro/teleport-inputs.CeJBMzcb_ZYeUzH.webp 750w, https://developer.pingidentity.com/blog/_astro/teleport-inputs.CeJBMzcb_2knfQF.webp 828w, https://developer.pingidentity.com/blog/_astro/teleport-inputs.CeJBMzcb_2cRabX.webp 1080w, https://developer.pingidentity.com/blog/_astro/teleport-inputs.CeJBMzcb_RD7L5.webp 1280w, https://developer.pingidentity.com/blog/_astro/teleport-inputs.CeJBMzcb_Z1htuxx.webp 1668w, https://developer.pingidentity.com/blog/_astro/teleport-inputs.CeJBMzcb_ZEowoj.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Passing Parsed Object Variables into a Form&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You probably noticed that your variables list looked different inside this new section. That’s because the Teleport only has access to the data that was passed into it or variables set at the Global or Flow Instance level. Just like a function, the teleport can only see its parameters, top level constants, or run-time variables.&lt;/p&gt;
&lt;p&gt;Back up to your Read Population by ID node (the one called after you discover an existing user), replace your HTTP node with a Teleport but this time use the &lt;strong&gt;Go to Start Node&lt;/strong&gt; trigger and select your Login teleport as the start node.&lt;/p&gt;
&lt;p&gt;Your variables for a PingOne User ID and Population Details will appear. Add in your PingOne User from your Find User node and the PingOne Population Details from the Find Population node.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Teleport Go To Start Node in which the inputs from gathering the population have been provided&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1206&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/teleport-inputs-start.Cy_rfs-O_2vswrk.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/teleport-inputs-start.Cy_rfs-O_Z1ugxhR.webp 640w, https://developer.pingidentity.com/blog/_astro/teleport-inputs-start.Cy_rfs-O_1CjyHT.webp 750w, https://developer.pingidentity.com/blog/_astro/teleport-inputs-start.Cy_rfs-O_ZJh7H6.webp 828w, https://developer.pingidentity.com/blog/_astro/teleport-inputs-start.Cy_rfs-O_hYVny.webp 1080w, https://developer.pingidentity.com/blog/_astro/teleport-inputs-start.Cy_rfs-O_ZRVv3y.webp 1280w, https://developer.pingidentity.com/blog/_astro/teleport-inputs-start.Cy_rfs-O_1yUdXe.webp 1668w, https://developer.pingidentity.com/blog/_astro/teleport-inputs-start.Cy_rfs-O_2vswrk.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Go to Teleport&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You’ve now set up a way to pass data into a teleport - basically, execute a function in your module. Apply, Save, Deploy, and Try the flow with a user that does exist. You should now hit the Password page.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The resulting show password screen, without any themes on the population&quot; loading=&quot;lazy&quot; width=&quot;926&quot; height=&quot;932&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-password.DI5Xg_vO_Z1FCO4F.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-password.DI5Xg_vO_u6P2f.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-password.DI5Xg_vO_Z29g0zR.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-password.DI5Xg_vO_1Bm1fi.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-password.DI5Xg_vO_Z1FCO4F.webp 926w&quot; /&gt;&amp;gt;
&lt;em&gt;The Password Page&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But the theme looks just like the last page! Let’s change that.&lt;/p&gt;
&lt;p&gt;Back in PingOne, under User Experience → Branding and Themes, go ahead and create a new theme. I’m using the base “Slate” theme for this example.&lt;/p&gt;
&lt;p&gt;When you’re feeling good about the theme, under Directory → Populations select your Population and under Configuration assign your new theme.&lt;/p&gt;
&lt;p&gt;The next time you go through the flow, you’ll see the theme change to your selected theme.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The resulting show password theme with the &amp;quot;Slate&amp;quot; theme&quot; loading=&quot;lazy&quot; width=&quot;1562&quot; height=&quot;1168&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/show-password-slate.C1FiDpfE_Z2qMacr.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/show-password-slate.C1FiDpfE_KFEIG.webp 640w, https://developer.pingidentity.com/blog/_astro/show-password-slate.C1FiDpfE_Z27P2q9.webp 750w, https://developer.pingidentity.com/blog/_astro/show-password-slate.C1FiDpfE_5GdiT.webp 828w, https://developer.pingidentity.com/blog/_astro/show-password-slate.C1FiDpfE_Z2hpPQ5.webp 1080w, https://developer.pingidentity.com/blog/_astro/show-password-slate.C1FiDpfE_29GWFD.webp 1280w, https://developer.pingidentity.com/blog/_astro/show-password-slate.C1FiDpfE_Z2qMacr.webp 1562w&quot; /&gt;&amp;gt;
&lt;em&gt;The Updated Theme&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Enforcing Password Policies based on the Population&lt;/h1&gt;
&lt;p&gt;You can set a unique Password Policy per Population so that each User segment can meet the security requirements set by their organization.&lt;/p&gt;
&lt;p&gt;Since a User can only be a part of a single Population, the default node already knows their password policy during login. From the Password Authentication Node, connect a PingOne Connector with a Check Password Trigger, adding the teleported p1UserId and the password you collected from the form.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Check Password node pulling in the user ID and collected password&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1130&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/check-password.BzwMkZ3i_Z1pc6l8.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/check-password.BzwMkZ3i_Z2oVqBf.webp 640w, https://developer.pingidentity.com/blog/_astro/check-password.BzwMkZ3i_Z1dR5I4.webp 750w, https://developer.pingidentity.com/blog/_astro/check-password.BzwMkZ3i_Z10K6bc.webp 828w, https://developer.pingidentity.com/blog/_astro/check-password.BzwMkZ3i_i4Vbx.webp 1080w, https://developer.pingidentity.com/blog/_astro/check-password.BzwMkZ3i_Z1PPXr3.webp 1280w, https://developer.pingidentity.com/blog/_astro/check-password.BzwMkZ3i_1hXR3M.webp 1668w, https://developer.pingidentity.com/blog/_astro/check-password.BzwMkZ3i_Z1pc6l8.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;The Check Password Node&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A password can be in more than one state - it’s valid, invalid, or it needs to be updated in some way (e.g. it’s expired, doesn’t meet the new password policy requirements). If you wanted to just check if the password is/isn’t valid, creating responses using the “All Triggers True” and “Any Trigger False” responses works just fine. But, if you’d like to perform additional branches on validation you can use an A == B (Multiple Conditions) Function Node to compare the &lt;code&gt;status&lt;/code&gt; of the Check Password response to the response options shown in the &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/platform/users/user-passwords/password-check.html&quot;&gt;Check Password API reference&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the sake of this example, we’ll create an outcome only on success - the error response will be handled for us.&lt;/p&gt;
&lt;p&gt;Wire up the Check Password Node to an HTTP connector with the Custom HTML Message trigger and give it the message “Password Status:” including the &lt;code&gt;status&lt;/code&gt; variable from your Check Password node. If you want to see all status information, set the path to be “Any Trigger Completes”.&lt;/p&gt;
&lt;p&gt;Now, when you log in with the correct password you’ll see the status information that you’d likely branch from in a more robust flow.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The resulting check password status message node. In this case, the message indicates that the password must be changed.&quot; loading=&quot;lazy&quot; width=&quot;1048&quot; height=&quot;854&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/check-password-status.9uAxonYQ_ZHJQcr.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/check-password-status.9uAxonYQ_Z7L5yr.webp 640w, https://developer.pingidentity.com/blog/_astro/check-password-status.9uAxonYQ_Z1iznoM.webp 750w, https://developer.pingidentity.com/blog/_astro/check-password-status.9uAxonYQ_Z1nqclk.webp 828w, https://developer.pingidentity.com/blog/_astro/check-password-status.9uAxonYQ_ZHJQcr.webp 1048w&quot; /&gt;&amp;gt;
&lt;em&gt;Returned Password Check Status&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Enforcing Login with an External IdP based on the Population&lt;/h1&gt;
&lt;p&gt;Some of your business groups and partnerships may have their own established Identity Provider that they’ll want to bring rather than having their users create and manage a separate account. Populations let you assign a default provider that, when a user federates with it, will associate them with the right Population (and as such the appropriate theming and managed administration).&lt;/p&gt;
&lt;p&gt;Your teleport has already been configured to pull in the external IdP ID tied to your population - we’ll just need to add the branching path that decides whether to log in with that IdP instead of locally.&lt;/p&gt;
&lt;p&gt;Connect your Teleport Start Node to a Function Connector with the “A is Empty” trigger and enable “Check undefined/null”. Connect the “All Triggers True” outcome to the Password Authentication node you made in the last section.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;An A is Empty function checking if the populationIdpId passed is undefined or null&quot; loading=&quot;lazy&quot; width=&quot;1890&quot; height=&quot;1050&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/local-login-check.BEd9Ppce_1ezFd4.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/local-login-check.BEd9Ppce_2hjeUM.webp 640w, https://developer.pingidentity.com/blog/_astro/local-login-check.BEd9Ppce_Z1AyXUk.webp 750w, https://developer.pingidentity.com/blog/_astro/local-login-check.BEd9Ppce_Z1ubmoW.webp 828w, https://developer.pingidentity.com/blog/_astro/local-login-check.BEd9Ppce_1OyMlH.webp 1080w, https://developer.pingidentity.com/blog/_astro/local-login-check.BEd9Ppce_ZOtnQA.webp 1280w, https://developer.pingidentity.com/blog/_astro/local-login-check.BEd9Ppce_Z11Tpc5.webp 1668w, https://developer.pingidentity.com/blog/_astro/local-login-check.BEd9Ppce_1ezFd4.webp 1890w&quot; /&gt;&amp;gt;
&lt;em&gt;Check for Local Login&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Next, connect the “Any Trigger False” outcome to a PingOne Authentication Connector with the trigger “Sign On with External Identity Provider”, using the &lt;code&gt;populationIdPId&lt;/code&gt;, the &lt;code&gt;populationId&lt;/code&gt;, and the &lt;code&gt;p1UserName&lt;/code&gt; (in the Login Hint) you collected in the teleport.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Tip:&lt;/strong&gt; If you don’t want users to be able to link to an existing account or create a new account from an external IdP, uncheck the “Link with PingOne User” checkbox.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt=&quot;The External IdP Login node providing the population ID and external IDP ID&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1195&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/external-idp.BdML65cZ_Z1L7IlC.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/external-idp.BdML65cZ_xwWDO.webp 640w, https://developer.pingidentity.com/blog/_astro/external-idp.BdML65cZ_Z2nIez0.webp 750w, https://developer.pingidentity.com/blog/_astro/external-idp.BdML65cZ_2ku5wN.webp 828w, https://developer.pingidentity.com/blog/_astro/external-idp.BdML65cZ_1yvpdR.webp 1080w, https://developer.pingidentity.com/blog/_astro/external-idp.BdML65cZ_1qJjMm.webp 1280w, https://developer.pingidentity.com/blog/_astro/external-idp.BdML65cZ_2jYWST.webp 1668w, https://developer.pingidentity.com/blog/_astro/external-idp.BdML65cZ_Z1L7IlC.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;External IdP Login&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To test, add an HTTP Connector with a custom HTML Message of your choosing - I’ve added the &lt;code&gt;statusCode&lt;/code&gt; from the external IdP Login. When you Log in with a Population that doesn’t have an External IdP configured, you should see the password page as you did before. But in the case you have an External IdP configured, you’ll redirect to their login page and then come back with your login status.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A message with the statuscode 200 indicating a successful login with an external IDP&quot; loading=&quot;lazy&quot; width=&quot;1030&quot; height=&quot;846&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/statuscode.DSX18ru6_Z1kx4Tn.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/statuscode.DSX18ru6_ZjhDG0.webp 640w, https://developer.pingidentity.com/blog/_astro/statuscode.DSX18ru6_1kCA9x.webp 750w, https://developer.pingidentity.com/blog/_astro/statuscode.DSX18ru6_Z1OdluR.webp 828w, https://developer.pingidentity.com/blog/_astro/statuscode.DSX18ru6_Z1kx4Tn.webp 1030w&quot; /&gt;&amp;gt;
&lt;em&gt;A Successful External IdP Login&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Dynamic Registration&lt;/h1&gt;
&lt;p&gt;We’ve created the Teleport for Logging in, but we haven’t handled the users who don’t exist yet. Hold and drag with your mouse to highlight all of your login teleport nodes and Duplicate the selection. Next:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Rename the Annotation and the Teleport Start node to “Registration”.&lt;/li&gt;
&lt;li&gt;In the Teleport Start Node, remove &lt;code&gt;p1UserId&lt;/code&gt; since a user doesn’t exist yet.&lt;/li&gt;
&lt;li&gt;Change the Password Authentication form to “Example - Registration” and pass in the &lt;code&gt;p1UserName&lt;/code&gt; into the username and email field values.&lt;/li&gt;
&lt;li&gt;Change the Check Password Trigger to the Create User Trigger and pass in the Username, Email, Password, and Population ID.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Finally, wire up the following connections in your New User branch at the top of your flow to the Go To Teleport for Registration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using an “All Triggers True” after the “Get Default Pop” node, using the population details from the Get Default Pop and the Username from the Username form.&lt;/li&gt;
&lt;li&gt;Using an “Any Triggers False” after the “More than One Pop Found?” node, using the top Population result from the “Read Pop(s) by Alt Id” node and the Username from the Username form.&lt;/li&gt;
&lt;li&gt;Connecting from the Dynamic Dropdown node (replacing the HTTP node), connect a PingOne Connector with a Find Population trigger using the selected population ID, and then wire the true outcome to the Registration teleport using the newfound population details. Alternatively, replace the Get Population node with a Custom Function that returns the appropriate population details from the previous call.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, when you enter a new user you are taken to the appropriate registration page depending on your Population preferences, automatically using the appropriate password policy and external IdP.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The registration page in which the user has to add their username, email, and password. The username and email address are auto-populated&quot; loading=&quot;lazy&quot; width=&quot;932&quot; height=&quot;1340&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-registration.CHSzgUV7_Z2dk84M.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-registration.CHSzgUV7_ZvOGet.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-registration.CHSzgUV7_Z2nK12B.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-registration.CHSzgUV7_wIGkI.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-registration.CHSzgUV7_Z2dk84M.webp 932w&quot; /&gt;&amp;gt;
&lt;em&gt;New User Registration&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Connecting the Flow to Your Application(s)&lt;/h1&gt;
&lt;p&gt;Up to this point we have used the “Try Flow” button to test out our DaVinci flow. While this approach is great for testing or for flows that execute actions without the need for returning user context, in the real world it’s highly likely that your flows will complete with a user logging into an Application with an active session from PingOne.&lt;/p&gt;
&lt;p&gt;To start, let’s make two more Teleports: one to return an authentication success and the other to return a failure.&lt;/p&gt;
&lt;p&gt;Duplicate just the annotation and the Start Teleport Node from your Login section and then change the Input Schema for your Login Success Teleport to only have the &lt;code&gt;p1UserId&lt;/code&gt; - that’s all we need to authenticate the user. Name the new section and teleport “Login Success”.&lt;/p&gt;
&lt;p&gt;From that node connect a PingOne Authentication Connector with the trigger “Return Success Response (Redirect Flows)”. Configure this node to use the User ID from the Teleport and leave the Authentication Method as “pwd” since we aren’t performing any additional form of risk or multifactor-based authentication. You’ll see in this node you can optionally decorate the ID and Access Tokens with custom claims as needed by your downstream application.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The return success response which is providing the p1userid collected in the teleport&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;783&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/return-success.Dnggibfn_ZxAUBv.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/return-success.Dnggibfn_ZPNk2p.webp 640w, https://developer.pingidentity.com/blog/_astro/return-success.Dnggibfn_Z9davF.webp 750w, https://developer.pingidentity.com/blog/_astro/return-success.Dnggibfn_Y6mrB.webp 828w, https://developer.pingidentity.com/blog/_astro/return-success.Dnggibfn_NdVMc.webp 1080w, https://developer.pingidentity.com/blog/_astro/return-success.Dnggibfn_Z1dGAEU.webp 1280w, https://developer.pingidentity.com/blog/_astro/return-success.Dnggibfn_Db9mP.webp 1668w, https://developer.pingidentity.com/blog/_astro/return-success.Dnggibfn_ZxAUBv.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;The Success Route&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Duplicate the nodes and annotations, renaming the new section and teleport “Login Failure”. Inside the teleport, change the input schema to capture the Error Message:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;object&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;properties&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;errorMessage&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Error Message&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;preferredControlType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;textField&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;enableParameters&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;errorMessage&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;errorDescription&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Error Description&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;preferredControlType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;textField&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;enableParameters&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;errorDescription&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;errorReason&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Error Reason&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;preferredControlType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;textField&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;enableParameters&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;errorReason&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, change the PingOne Authentication Connector’s trigger to “Return Error Response” with a custom error message that uses the inputs you just provided in the Teleport’s Input Schema (I set the Error Message to &lt;code&gt;invalid_request&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The login failure node which includes the error message responses from the teleport&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1457&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/return-failure.x7weQMKc_Z1jFxLV.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/return-failure.x7weQMKc_Z2mVBFS.webp 640w, https://developer.pingidentity.com/blog/_astro/return-failure.x7weQMKc_Z2dEFVQ.webp 750w, https://developer.pingidentity.com/blog/_astro/return-failure.x7weQMKc_Z2tAVgy.webp 828w, https://developer.pingidentity.com/blog/_astro/return-failure.x7weQMKc_Z2d6xEY.webp 1080w, https://developer.pingidentity.com/blog/_astro/return-failure.x7weQMKc_Z90Xaf.webp 1280w, https://developer.pingidentity.com/blog/_astro/return-failure.x7weQMKc_iMn9U.webp 1668w, https://developer.pingidentity.com/blog/_astro/return-failure.x7weQMKc_Z1jFxLV.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;The Success and Failure Teleports&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now that you have a means to inform the downstream applications of a success or failure, let’s wire it up to the appropriate paths in the flow.&lt;/p&gt;


















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;From Node&lt;/th&gt;&lt;th&gt;Connect Go To Teleport&lt;/th&gt;&lt;th&gt;With Variables&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Check Password, Inside Login&lt;/td&gt;&lt;td&gt;Login Success (All True)&lt;/td&gt;&lt;td&gt;&lt;code&gt;p1UserId&lt;/code&gt; from the teleport, or the &lt;code&gt;id&lt;/code&gt; returned from Check Password&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Check Password, Inside Login&lt;/td&gt;&lt;td&gt;Login Failure (Any False)&lt;/td&gt;&lt;td&gt;The error details from Check Password&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;External IdP Node, Inside Login&lt;/td&gt;&lt;td&gt;Login Success (All True)&lt;/td&gt;&lt;td&gt;&lt;code&gt;p1UserId&lt;/code&gt; from the teleport, or the &lt;code&gt;id&lt;/code&gt; returned from External IdP Login&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;External IdP Node, Inside Login&lt;/td&gt;&lt;td&gt;Login Failure (Any False)&lt;/td&gt;&lt;td&gt;The error details from External IdP Node&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Create User, Inside Registration&lt;/td&gt;&lt;td&gt;Login Success (All True)&lt;/td&gt;&lt;td&gt;The &lt;code&gt;id&lt;/code&gt; returned from Create User&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Create User, Inside Registration&lt;/td&gt;&lt;td&gt;Login Failure (Any False)&lt;/td&gt;&lt;td&gt;The error details from Create User&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;External IdP Node, Inside Registration&lt;/td&gt;&lt;td&gt;Login Success (All True)&lt;/td&gt;&lt;td&gt;The &lt;code&gt;id&lt;/code&gt; returned from the External IdP Login&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;External IdP Node, Inside Registration&lt;/td&gt;&lt;td&gt;Login Failure (Any False)&lt;/td&gt;&lt;td&gt;The error details from External IdP Node&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;img alt=&quot;The connected teleports to the nodes described in the above table&quot; loading=&quot;lazy&quot; width=&quot;1808&quot; height=&quot;1556&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/teleports.DuoVaG1s_ZnWUWl.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/teleports.DuoVaG1s_wGS5q.webp 640w, https://developer.pingidentity.com/blog/_astro/teleports.DuoVaG1s_BHtK2.webp 750w, https://developer.pingidentity.com/blog/_astro/teleports.DuoVaG1s_1Ruj8Y.webp 828w, https://developer.pingidentity.com/blog/_astro/teleports.DuoVaG1s_244HyC.webp 1080w, https://developer.pingidentity.com/blog/_astro/teleports.DuoVaG1s_2sf4Fr.webp 1280w, https://developer.pingidentity.com/blog/_astro/teleports.DuoVaG1s_2b1bPb.webp 1668w, https://developer.pingidentity.com/blog/_astro/teleports.DuoVaG1s_ZnWUWl.webp 1808w&quot; /&gt;&amp;gt;
&lt;em&gt;The Connected Teleports&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Learning:&lt;/strong&gt; PingOne Flows&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In order for our DaVinci flow to be connected to an application in PingOne, we will need to make it a PingOne Flow.&lt;/p&gt;
&lt;p&gt;PingOne operates with DaVinci via an OIDC redirect. When a user attempts to log in to an application in PingOne, and DaVinci is protecting that application (via a &lt;strong&gt;Flow Policy&lt;/strong&gt;), PingOne will redirect the user to DaVinci to perform its actions upon which it redirects back to PingOne to generate a user session and log the user in. The handoff looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A diagram which explains the flow between pingone, an application, and DaVinci&quot; loading=&quot;lazy&quot; width=&quot;1226&quot; height=&quot;521&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/pingone-to-dv-diagram.CCxZhyAC_1S31up.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/pingone-to-dv-diagram.CCxZhyAC_2kgOjr.webp 640w, https://developer.pingidentity.com/blog/_astro/pingone-to-dv-diagram.CCxZhyAC_1hBcEV.webp 750w, https://developer.pingidentity.com/blog/_astro/pingone-to-dv-diagram.CCxZhyAC_1czAb0.webp 828w, https://developer.pingidentity.com/blog/_astro/pingone-to-dv-diagram.CCxZhyAC_10GPdJ.webp 1080w, https://developer.pingidentity.com/blog/_astro/pingone-to-dv-diagram.CCxZhyAC_1S31up.webp 1226w&quot; /&gt;&amp;gt;
&lt;em&gt;DaVinci with PingOne&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Underneath your Flow Settings (click the three dots in the top-right, select Flow Settings and under the General tab) enable PingOne Flow. This converts the flow to run for either OIDC or SAML authentication with PingOne.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The settings switch that enables the flow to be a PingOne Flow. It is turned on&quot; loading=&quot;lazy&quot; width=&quot;1484&quot; height=&quot;716&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/pingone-flow.CQjbx4B4_ZxOIpt.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/pingone-flow.CQjbx4B4_Bk95t.webp 640w, https://developer.pingidentity.com/blog/_astro/pingone-flow.CQjbx4B4_Zd79Oq.webp 750w, https://developer.pingidentity.com/blog/_astro/pingone-flow.CQjbx4B4_Z1oFWXW.webp 828w, https://developer.pingidentity.com/blog/_astro/pingone-flow.CQjbx4B4_Z2erwig.webp 1080w, https://developer.pingidentity.com/blog/_astro/pingone-flow.CQjbx4B4_Z1N472l.webp 1280w, https://developer.pingidentity.com/blog/_astro/pingone-flow.CQjbx4B4_ZxOIpt.webp 1484w&quot; /&gt;&amp;gt;
&lt;em&gt;Enabling a PingOne Flow&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once you do this (and deploy!) you’ll notice that a little “P1” icon appears next to your title and if you try to use the “Try Flow” button you’ll be redirected to an error page that indicates that this flow requires PingOne to run.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The default error message that appears when attempting to use the &amp;quot;Try Now&amp;quot; button on a PingOne flow. It says that the flow cannot be executed because it is configured as a PingOne flow&quot; loading=&quot;lazy&quot; width=&quot;1604&quot; height=&quot;356&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/pingone-flow-error.DyZurrta_Z1Ri313.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/pingone-flow-error.DyZurrta_Z2mEobS.webp 640w, https://developer.pingidentity.com/blog/_astro/pingone-flow-error.DyZurrta_1KSiW0.webp 750w, https://developer.pingidentity.com/blog/_astro/pingone-flow-error.DyZurrta_ZuP50N.webp 828w, https://developer.pingidentity.com/blog/_astro/pingone-flow-error.DyZurrta_1niOXe.webp 1080w, https://developer.pingidentity.com/blog/_astro/pingone-flow-error.DyZurrta_g5nQj.webp 1280w, https://developer.pingidentity.com/blog/_astro/pingone-flow-error.DyZurrta_Z1Ri313.webp 1604w&quot; /&gt;&amp;gt;
&lt;em&gt;Hitting “Try Flow” with a PingOne Flow&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;DaVinci connects flows to PingOne applications using Flow Policies. Flow Policies let us define what flows and flow versions run, when they run, and what statistics we’d like to gather when users are driven through the flows (say, a successful outcome or a failure).&lt;/p&gt;
&lt;p&gt;On the DaVinci sidebar, click on “Applications”. You should see a page that includes a PingOne SSO Connection. You can add more Flow Policies to the existing PingOne Connection or create a new one - this lets you create bundles of Policy categories, making it easier to identify when selecting the Policy in PingOne.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The DaVinci Applications view in which the default PingOne SSO connection app is displayed&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1483&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-app.CWo3Ldu1_1IecGH.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-app.CWo3Ldu1_Z2elRu.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-app.CWo3Ldu1_sdjJn.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-app.CWo3Ldu1_2eegLM.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-app.CWo3Ldu1_28Vimj.webp 1080w, https://developer.pingidentity.com/blog/_astro/flow-app.CWo3Ldu1_JDbUk.webp 1280w, https://developer.pingidentity.com/blog/_astro/flow-app.CWo3Ldu1_2enVPr.webp 1668w, https://developer.pingidentity.com/blog/_astro/flow-app.CWo3Ldu1_1IecGH.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;DaVinci Applications&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Create “Add Application” and name it “Dynamic Populations”, then click on the created Application. You’ll see that this application is its own OIDC client with a unique client ID and client secret, and that the “Company ID” is actually the ID of the PingOne environment that this instance of DaVinci is tied to. This lets you take that same application and use it with non-PingOne systems you want DaVinci to interact with.&lt;/p&gt;
&lt;p&gt;Select the “Flow Policy” tab and click on “Add Flow Policy”. &lt;strong&gt;Make sure you add a name in the first step&lt;/strong&gt; (in my case I’m doing “Population Login and Registration”) and select “PingOne Flow Policy” since we will be interacting with a PingOne application. An option to Bypass flows will appear - skip that section and click Next.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The first page in adding a flow policy. PingOne Flow Policy is selected and a title has been written&quot; loading=&quot;lazy&quot; width=&quot;1838&quot; height=&quot;1508&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-policy-1.CNd-7bfv_Z2jcX11.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-policy-1.CNd-7bfv_A78B2.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-policy-1.CNd-7bfv_Z1TJR1J.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-policy-1.CNd-7bfv_Z2rKRdh.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-policy-1.CNd-7bfv_ZAeVE6.webp 1080w, https://developer.pingidentity.com/blog/_astro/flow-policy-1.CNd-7bfv_wMt39.webp 1280w, https://developer.pingidentity.com/blog/_astro/flow-policy-1.CNd-7bfv_M5zUs.webp 1668w, https://developer.pingidentity.com/blog/_astro/flow-policy-1.CNd-7bfv_Z2jcX11.webp 1838w&quot; /&gt;&amp;gt;
&lt;em&gt;Adding a Flow Policy - Step 1&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You’ll now see a picker of PingOne-configured flows that you have created. Select the flow you’ve been working on (Dynamic Experiences with Populations) and click on “Latest Version”.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Tip:&lt;/strong&gt; DaVinci holds up to 100 past versions of your deployed flows - meaning that you can “lock” an application down to a specific version of a flow while you develop a new one without impacting your users’ experience.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt=&quot;The second page in adding a flow policy. The specific flows and flow versions are selected&quot; loading=&quot;lazy&quot; width=&quot;1790&quot; height=&quot;2000&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-policy-2.BafU73Q-_Z1J7Kkh.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-policy-2.BafU73Q-_ZKLm5S.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-policy-2.BafU73Q-_lSX3v.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-policy-2.BafU73Q-_2nbaC7.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-policy-2.BafU73Q-_ZGInFm.webp 1080w, https://developer.pingidentity.com/blog/_astro/flow-policy-2.BafU73Q-_Z2iHRVN.webp 1280w, https://developer.pingidentity.com/blog/_astro/flow-policy-2.BafU73Q-_Qbh38.webp 1668w, https://developer.pingidentity.com/blog/_astro/flow-policy-2.BafU73Q-_Z1J7Kkh.webp 1790w&quot; /&gt;&amp;gt;
&lt;em&gt;Adding a Flow Policy - Step 2&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Next, set your selected flow to 100% distribution and set the PingOne Authentication (Login Success) node as your Success Node.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⭐ DaVinci Tip:&lt;/strong&gt; DaVinci Flow Policies let you select multiple flows and flow versions in the same policy and then set a distribution to each instance within that policy. That, coupled with the success node definitions, allows you to A/B test and gather feedback on different approaches with your users simultaneously.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt=&quot;The final step in adding a flow policy, in which the weight of each selected flow as well as their success criteria is defined&quot; loading=&quot;lazy&quot; width=&quot;1586&quot; height=&quot;1460&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-policy-3.D2QWSjSm_1wbeww.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-policy-3.D2QWSjSm_Z1sdo6T.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-policy-3.D2QWSjSm_1WkrPb.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-policy-3.D2QWSjSm_QTBPD.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-policy-3.D2QWSjSm_YkDMx.webp 1080w, https://developer.pingidentity.com/blog/_astro/flow-policy-3.D2QWSjSm_5Nlhx.webp 1280w, https://developer.pingidentity.com/blog/_astro/flow-policy-3.D2QWSjSm_1wbeww.webp 1586w&quot; /&gt;&amp;gt;
&lt;em&gt;Adding a Flow Policy - Step 3&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At this point you have defined an Application and a Flow Policy within that Application. Let’s assign that policy to PingOne to be evaluated when a user attempts to login.&lt;/p&gt;
&lt;p&gt;Back in PingOne, click on the “Applications” tab and submenu. Feel free to use a custom SAML or OIDC application, but for the sake of simplicity I’m going to use the “PingOne Self-Service - MyAccount” application.&lt;/p&gt;
&lt;p&gt;Clicking on your application, select the “Policies” tab and click “Add Policies”. You’ll see a DaVinci tab in the subsequent editor - click on that tab and then check the new policy you just made.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The application editing window in PingOne in which policies can be edited. The DaVinci Flow policy has been checked&quot; loading=&quot;lazy&quot; width=&quot;1574&quot; height=&quot;974&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/flow-policy-assign.bVVyWi7L_iPd69.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/flow-policy-assign.bVVyWi7L_1uRrOt.webp 640w, https://developer.pingidentity.com/blog/_astro/flow-policy-assign.bVVyWi7L_Z1PVjxr.webp 750w, https://developer.pingidentity.com/blog/_astro/flow-policy-assign.bVVyWi7L_Z2uLNlz.webp 828w, https://developer.pingidentity.com/blog/_astro/flow-policy-assign.bVVyWi7L_ETJRw.webp 1080w, https://developer.pingidentity.com/blog/_astro/flow-policy-assign.bVVyWi7L_Z11xNzO.webp 1280w, https://developer.pingidentity.com/blog/_astro/flow-policy-assign.bVVyWi7L_iPd69.webp 1574w&quot; /&gt;&amp;gt;
&lt;em&gt;Assigning a Flow Policy to a PingOne Application&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;By assigning this policy you have informed PingOne that for a user to successfully authenticate into this application they are required to first go through the DaVinci flow you created.&lt;/p&gt;
&lt;p&gt;On the Overview tab, click on the Home Page URL of the Self Service Application (or in the case of your OIDC/SAML App, the appropriate calling URL) and open in an incognito tab. You’ll be greeted with a familiar sight - your DaVinci flow! Successfully complete login and you’ll be redirected to your My Account page (or wherever your app completes).&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The My Account Page for the example&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;864&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/myaccount.DYFccrL8_18dxQj.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/myaccount.DYFccrL8_ZVHllY.webp 640w, https://developer.pingidentity.com/blog/_astro/myaccount.DYFccrL8_GKPJL.webp 750w, https://developer.pingidentity.com/blog/_astro/myaccount.DYFccrL8_vaw72.webp 828w, https://developer.pingidentity.com/blog/_astro/myaccount.DYFccrL8_Z1duXAR.webp 1080w, https://developer.pingidentity.com/blog/_astro/myaccount.DYFccrL8_Z23Pkc0.webp 1280w, https://developer.pingidentity.com/blog/_astro/myaccount.DYFccrL8_Z1AWrzK.webp 1668w, https://developer.pingidentity.com/blog/_astro/myaccount.DYFccrL8_18dxQj.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;A Logged-In User&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;You now have a way to dynamically branch login and registration experiences based on contextual data supplied by the User and Population. You’ve also built a toolbox of relevant skills in DaVinci to build more complex, interesting, and incredible flows confidently.&lt;/p&gt;
&lt;p&gt;DaVinci is an incredibly powerful tool, and coupling it with the strong capabilities and services within PingOne you can design the most secure and seamless experiences for your users.&lt;/p&gt;
&lt;p&gt;For a copy of the completed flow, go &lt;a href=&quot;https://github.com/gwizdala/lib-ping/blob/main/How-Tos/davinci-primer-dynamic-populations/dynamic_experiences_with_populations.json&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Other Helpful Tricks&lt;/h1&gt;
&lt;p&gt;There are some other capabilities in PingOne that may prove useful as you work your way through more complicated flows.&lt;/p&gt;
&lt;h2&gt;Variables&lt;/h2&gt;
&lt;p&gt;Variables allow us to set data that can either be stored and referenced at a particular instance of a running flow (that’s a Flow Instance Variable, like a module-level constant) or globally across all executions (that’s a Global Variable or Company Context, and it’s exactly what you think).&lt;/p&gt;
&lt;p&gt;You can set variables within nodes in Flows (you actually did that in the “Set Company &amp;amp; Logo” node) as well as via the “Variables” tab on the left menu. It’s likely that you’ll set Flow Instance Variables dynamically within a Flow and Global Variables from within the menu (as those are normally static for all flow executions). A great example use case of Global Variables is the DNA Flows mentioned before - check out their &lt;a href=&quot;https://github.com/pingone-davinci/davinci-dna-flows/blob/main/DV-VariableObject-ConfigurationGuide.md&quot;&gt;configuration guide&lt;/a&gt; for an example of what you can store.&lt;/p&gt;
&lt;h2&gt;Parsing Objects from a Teleport&lt;/h2&gt;
&lt;p&gt;If you had a complex object that you wanted to pass directly into a teleport, it’s useful to parse that object in a subsequent Custom Function Connector.&lt;/p&gt;
&lt;p&gt;The function within that connector should be incredibly simple - return the name of the object you want to return and parse the passed object into JSON.&lt;/p&gt;
&lt;p&gt;As an example, if you wanted to pass the Population object into a teleport directly you could create a script that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exports&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;}) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;&apos;population&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(params.populationString)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a production use case you may want to add some parametrization and checking to validate that your object has all of the values you’re looking for.&lt;/p&gt;
&lt;p&gt;Next, set your Output Schema to map to the object you want to reference.&lt;/p&gt;
&lt;p&gt;Using the same use case, your output schema may look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;output&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;object&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;properties&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; 			&quot;population&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;object&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;population&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Population&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;properties&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&quot;id&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Population ID&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&quot;name&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Population Name&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&quot;preferredLanguage&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Preferred Language&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;preferredLanguage&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&quot;passwordPolicy&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;object&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Password Policy&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;passwordPolicy&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;properties&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&quot;id&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Password Policy ID&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&quot;theme&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;object&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Theme&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;theme&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;properties&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&quot;id&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Theme ID&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&quot;defaultIdentityProvider&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;object&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Default Identity Provider&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;defaultIdentityProvider&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;properties&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&quot;id&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Default Identity Provider ID&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&quot;type&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;string&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Default Identity Provider Type&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&quot;propertyName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;type&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach gives us a parsed object we can reference in later nodes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do you have thoughts or questions on this article? Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.1PUsVrVE_Z16vVkj.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Introducing the DaVinci MCP Server</title><link>https://developer.pingidentity.com/blog/introducing-the-davinci-mcp-server/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/introducing-the-davinci-mcp-server/</guid><description>Introducing the DaVinci MCP Server: Connect AI assistants to your PingOne DaVinci environment for faster flow exploration, validation, and troubleshooting.</description><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As the market moved toward AI assistants and agent-based workflows, I kept coming back to one question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What does this mean for identity orchestration platforms?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;More specifically, what should identity orchestration look like when AI assistants become part of the developer workflow?&lt;/p&gt;
&lt;p&gt;Identity journeys are not just pieces of code. They represent onboarding, authentication, MFA, recovery, fraud checks, and access decisions. They are the product of decisions made by architects, developers, product managers, and security teams, often over many months, sometimes by people who have since moved on. If AI assistants are going to help teams build and operate software, they also need a better way to understand the identity journeys behind those experiences.&lt;/p&gt;
&lt;p&gt;That realization came to me through a very ordinary scenario.&lt;/p&gt;
&lt;h2&gt;Life Before AI-Assisted Orchestration&lt;/h2&gt;
&lt;h3&gt;When a Simple Support Ticket Becomes an Identity Investigation&lt;/h3&gt;
&lt;p&gt;Silvia is a support agent at ACME Corp. She has received several tickets from users who cannot register their FIDO2 device during account creation. She knows that MFA device onboarding runs through DaVinci, but she does not know which flow is involved, where the failure is happening, or who to ask first.&lt;/p&gt;
&lt;p&gt;Traditionally, her path forward looks like this: escalate to the flow development team, wait for someone to pick up the ticket, have them identify which application and flow policy is in scope, inspect the connector configuration, review execution logs, and determine whether the failure is in the flow logic, the MFA connector, a policy decision, the user’s device, or something upstream in PingOne.&lt;/p&gt;
&lt;p&gt;The customer issue is simple to describe. The investigation is not.&lt;/p&gt;
&lt;p&gt;Silvia does not need to become a DaVinci expert. But she does need a faster way to ask operational questions: &lt;em&gt;Which flow handles this onboarding journey? Where does FIDO registration happen? Which connector is used? What did the last ten failed executions look like? What should I check first?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Right now, those questions require a specialist. They do not have to.&lt;/p&gt;
&lt;h2&gt;Need of the Hour&lt;/h2&gt;
&lt;p&gt;These are situations where the need for an MCP server becomes imminent, as a practical way to make DaVinci administration and troubleshooting easier.&lt;/p&gt;
&lt;p&gt;If an AI assistant can securely access the structure of a DaVinci environment, it can help people like Silvia understand flows faster, identify dependencies, validate configuration, and troubleshoot failures without starting from scratch every time.&lt;/p&gt;
&lt;h2&gt;A Quick Word on MCP&lt;/h2&gt;
&lt;p&gt;Before we go further, MCP, or the Model Context Protocol, is an open standard introduced by Anthropic that allows AI assistants to connect to external tools and data sources through a common interface. Instead of fragmented, custom integrations between AI tools and every system a team uses, MCP provides a standard protocol for connecting AI assistants to the systems &lt;strong&gt;where context actually lives&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For identity orchestration, that &lt;strong&gt;context&lt;/strong&gt; lives in flows, applications, connectors, variables, forms, flow versions, validation results, and execution history.&lt;/p&gt;
&lt;p&gt;MCP is the bridge. The DaVinci MCP Server is what sits on the other side of it.&lt;/p&gt;
&lt;p&gt;For a deeper look at MCP’s architecture, workflow, and security model, see the &lt;a href=&quot;https://developer.pingidentity.com/identity-for-ai/agents/idai-what-is-mcp.html&quot;&gt;Ping Identity MCP documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The DaVinci MCP Server&lt;/h2&gt;
&lt;p&gt;Today we are introducing the &lt;a href=&quot;https://developer.pingidentity.com/build-with-ai/davinci-mcp-server/overview.html&quot;&gt;&lt;strong&gt;DaVinci MCP Server&lt;/strong&gt;&lt;/a&gt;, an open-source MCP server that gives MCP-compatible AI assistants secure, structured access to your DaVinci environment.&lt;/p&gt;
&lt;p&gt;This is our first step toward making DaVinci context available to AI. The current release focuses entirely on &lt;strong&gt;read access&lt;/strong&gt;; exploration, inspection, validation, and execution monitoring. The goal for this first release is to help teams understand their DaVinci environments faster, and then gradually move towards automating changes to DaVinci resources.&lt;/p&gt;
&lt;p&gt;The MCP server includes tools organized into two collections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;davinci_admin:&lt;/strong&gt; Core administrative tools for exploring your environment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List and inspect flows, flow versions, and full flow definitions including node graphs&lt;/li&gt;
&lt;li&gt;List applications and application flow policies&lt;/li&gt;
&lt;li&gt;Discover available connectors and deployed connector instances&lt;/li&gt;
&lt;li&gt;Get details on variables and forms&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;davinci_flow_troubleshooting:&lt;/strong&gt; Specialized tools for validation and debugging flow configurations and troubleshooting flow executions&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Life After AI-Assisted Orchestration&lt;/h2&gt;
&lt;p&gt;The first time this becomes useful is not some dramatic moment where an AI agent fixes an entire identity journey by itself.&lt;/p&gt;
&lt;p&gt;It is a moment where support agents like Silvia can directly ask, &lt;em&gt;“Where are users failing during FIDO registration?”&lt;/em&gt; and getting a place to start instead of an escalation queue.&lt;/p&gt;
&lt;p&gt;It is an architect asking, &lt;em&gt;“Is this flow ready to deploy?”&lt;/em&gt; and seeing validation errors and warnings explained in plain language, not buried in a UI they have to navigate manually.&lt;/p&gt;
&lt;p&gt;The exact time savings will vary by environment, flow complexity, and familiarity with DaVinci. But the difference in the &lt;strong&gt;starting point&lt;/strong&gt; is consistent. With the DaVinci MCP Server, an AI assistant can surface the relevant flows, dependencies, validation results, and execution details at the start of the conversation, not at the end of a multi-hour investigation.&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Persona&lt;/th&gt;&lt;th&gt;Situation&lt;/th&gt;&lt;th&gt;Without DaVinci MCP Server&lt;/th&gt;&lt;th&gt;With DaVinci MCP Server&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;New developer / architect&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Inherited an onboarding journey, needs to improve drop-off rates&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Days to weeks:&lt;/strong&gt; learn DaVinci concepts, inspect flows and identify dependencies manually.&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Hours&lt;/strong&gt;: ask the assistant to explain the flow, identify dependencies in business terms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Support / operations team&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Production issue reported, team does not know which flow or node is responsible&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Hours or days&lt;/strong&gt; of back-and-forth between support and identity admins, starting with no context&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Faster first response&lt;/strong&gt;: provide user and approximate timelines and get details on point of failure&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Identity architect&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Review whether a flow is production-ready&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Hours&lt;/strong&gt; of manual review across the flow ecosystem, and known best practices&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Minutes&lt;/strong&gt;: the assistant validates the flow configs and highlights blockers&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Developer&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Make changes to a flow&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Hours&lt;/strong&gt; of manual inspection of the flow and its references&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Minutes&lt;/strong&gt;: the assistant generates a flow dependency summary before the developer touches anything&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Product manager / business owner&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Onboarding conversion dropped, wants to understand why&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Days or weeks&lt;/strong&gt;: Wait for technical teams to manually explain flows and correlate behavior&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Hours&lt;/strong&gt;: Get understanding of the onboarding journey in business terms and ask more focused follow-up questions&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Once teams see how much time is spent on context discovery and first-triage work, the natural next question becomes: which of these workflows can we automate? The read-only tools in this release are the foundation, but once you understand what your AI assistant can see and reason about, you start seeing which repetitive, manual, and rule-based investigation tasks are candidates for automation.&lt;/p&gt;
&lt;p&gt;The key takeaways are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;start with simple things,&lt;/li&gt;
&lt;li&gt;build confidence in how AI interacts with your DaVinci environment,&lt;/li&gt;
&lt;li&gt;and progressively hand off more of the first-response and review work to AI-assisted or fully automated workflows.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;The DaVinci MCP Server runs locally on your machine. Before you begin, make sure you have:&lt;/p&gt;
&lt;h3&gt;Local Prerequisites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Node.js v22.0.0 or higher (&lt;a href=&quot;https://nodejs.org/en/download/package-manager&quot;&gt;Download &amp;amp; Install&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;An MCP-compatible AI client such as Claude Code, VS Code with Cline, Cursor, Gemini CLI, or Claude Desktop&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;PingOne Prerequisites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A PingOne subscription (licensed or &lt;a href=&quot;https://www.pingidentity.com/en/try-ping.html&quot;&gt;free trial&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;DaVinci enabled on your PingOne environment (Admin Console → Environments → Services → DaVinci)&lt;/li&gt;
&lt;li&gt;A PingOne Worker Application configured with:
&lt;ul&gt;
&lt;li&gt;Authorization Code + PKCE&lt;/li&gt;
&lt;li&gt;redirect URI &lt;code&gt;http://127.0.0.1:7474/callback&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Token Endpoint Authentication Method set to None&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A PingOne user with at least &lt;strong&gt;DaVinci Admin Read Only&lt;/strong&gt; role assigned&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Configuration Values You’ll Need from PingOne Admin Console&lt;/h3&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Variable&lt;/th&gt;&lt;th&gt;Where to find it&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;DAVINCI_MCP_ENVIRONMENT_ID&lt;/td&gt;&lt;td&gt;Settings → Environment Properties&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AUTHORIZATION_CODE_CLIENT_ID&lt;/td&gt;&lt;td&gt;Applications → your Worker App → Overview&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ROOT_DOMAIN&lt;/td&gt;&lt;td&gt;pingone.com (NA), pingone.eu (EU), pingone.asia/pingone.au (APAC), or your custom domain&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Add the Server to Your AI Client&lt;/h3&gt;
&lt;p&gt;For Claude Code CLI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; mcp&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; --transport&lt;/span&gt;&lt;span&gt; stdio&lt;/span&gt;&lt;span&gt; davinci&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --env&lt;/span&gt;&lt;span&gt; DAVINCI_MCP_ENVIRONMENT_ID=&quot;your-environment-id&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --env&lt;/span&gt;&lt;span&gt; AUTHORIZATION_CODE_CLIENT_ID=&quot;your-client-id&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --env&lt;/span&gt;&lt;span&gt; ROOT_DOMAIN=&quot;pingone.com&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --&lt;/span&gt;&lt;span&gt; npx&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; @ping-identity/davinci-mcp-server&lt;/span&gt;&lt;span&gt; start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For other clients (Claude Desktop, Cursor, VS Code Cline, Gemini CLI), add the equivalent JSON block to your MCP client configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;mcpServers&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;davinci&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;command&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;npx&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;args&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;-y&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;@ping-identity/davinci-mcp-server&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;start&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;env&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;DAVINCI_MCP_ENVIRONMENT_ID&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;your-environment-id&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;AUTHORIZATION_CODE_CLIENT_ID&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;your-client-id&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;ROOT_DOMAIN&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;pingone.com&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;VS Code and Cursor users can also use the one-click install deeplinks in the &lt;a href=&quot;https://github.com/pingidentity/davinci-mcp-server&quot;&gt;GitHub repository&lt;/a&gt;. For advanced configuration, filtering which tools or collections are exposed to the AI assistant, see the &lt;a href=&quot;https://developer.pingidentity.com/build-with-ai/davinci-mcp-server/overview.html&quot;&gt;full setup guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Security&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;OAuth 2.0 with PKCE&lt;/strong&gt; Every tool call is authenticated using OAuth 2.0 Authorization Code flow with PKCE. Your password is never stored. The resulting access token is saved in your OS-native keychain, the same place your browser stores credentials.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read-only operations&lt;/strong&gt; The current release is entirely read-only. No flow mutations, no deployments, no configuration changes are possible through these tools. There is nothing to audit because nothing is written.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Least privilege&lt;/strong&gt; The agent has the same permissions as your authenticated PingOne user. Use the &lt;strong&gt;DaVinci Admin Read Only&lt;/strong&gt; role. Do not authenticate with an account that has write or admin-level access unless you have a specific reason and have reviewed the implications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trust your client&lt;/strong&gt; Only run this server with AI clients you trust. Review agent prompts and tool calls before deploying AI-assisted workflows to production.&lt;/p&gt;
&lt;h2&gt;What to Do Next&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Try it in a real scenario.&lt;/strong&gt; Think about something you or a teammate spent too long on recently, understanding a flow, diagnosing a failure, preparing for a review. Try it again with the MCP server connected to an AI assistant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Think about your workflows.&lt;/strong&gt; Where does your team spend time rediscovering context? Where are the first-triage steps that are repetitive and rule-based? Those are the candidates for AI-assisted or eventually automated workflows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Share what you find.&lt;/strong&gt; The Ping Identity developer community is where use cases get refined. What worked, what did not, what you wish the server could do, that feedback shapes the roadmap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Raise issues on the repository.&lt;/strong&gt; If you hit a bug, a gap, or if you have an enhancement request &lt;a href=&quot;https://github.com/pingidentity/davinci-mcp-server/issues&quot;&gt;open an issue&lt;/a&gt;. We are listening.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pingidentity/davinci-mcp-server&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/@ping-identity/davinci-mcp-server&quot;&gt;NPM Package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.pingidentity.com/build-with-ai/davinci-mcp-server/overview.html&quot;&gt;PingOne DaVinci MCP Server Developer Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pingidentity.com/davinci/release_notes/davinci_release_notes.html&quot;&gt;PingOne DaVinci Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://modelcontextprotocol.io/specification/2025-11-25&quot;&gt;Model Context Protocol Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc7636&quot;&gt;OAuth 2.0 PKCE - RFC 7636&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.pingidentity.com/blog/introducing-the-pingone-mcp-server/&quot;&gt;PingOne MCP Server Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.pingidentity.com/blog/introducing-the-aic-mcp-server/&quot;&gt;PingOne AIC Server Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.KoQ5fhOb_ZF6ODT.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Delegated Administration in PingOne</title><link>https://developer.pingidentity.com/blog/delegated-administration-in-pingone/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/delegated-administration-in-pingone/</guid><description>Enable your administrators, customers, and partners to manage their data, config, and users within PingOne</description><pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This post was originally published on &lt;a href=&quot;https://gwizkid.com/posts/delegated-administration-in-pingone/&quot;&gt;Gwiz Kid&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Your business is likely not just one set of users. In today’s world, identity stretches beyond the borders of siloed Internal (Workforce Employee) or External (Customer) user engagement and relationships. And as that line blurs, so does the process of user administration.&lt;/p&gt;
&lt;p&gt;PingOne, Ping’s Multi-Tenant SaaS solution, provides a robust means to delegate and manage administrative capabilities across the platform.&lt;/p&gt;
&lt;p&gt;This Guide will teach you how you can create and manage delegated administration of Policies and Users within the PingOne Platform. It is broken down into the following parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#definitions&quot;&gt;Defining Key PingOne concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#delegated-administration&quot;&gt;Delegating Administration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#extending-delegated-administration&quot;&gt;Extending Delegated Administration&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By the end of this Guide you’ll have configured multiple Environments with their own Applications, Populations, Groups, and Users. Additionally, you’ll have assigned Delegated Administrators to oversee capabilities within these Environments.&lt;/p&gt;
&lt;p&gt;This Guide expects a beginner level of familiarity with the PingOne administrative console. If you haven’t set one up yet, be sure to get access to a PingOne instance (register for a trial &lt;a href=&quot;https://www.pingidentity.com/en/try-ping.html&quot;&gt;here&lt;/a&gt;) so that you can follow along.&lt;/p&gt;
&lt;p&gt;But before we get into the details, let’s go over some quick platform definitions.&lt;/p&gt;
&lt;h1&gt;Definitions&lt;/h1&gt;
&lt;p&gt;Delegated Administration in PingOne will primarily interact with &lt;strong&gt;Environments&lt;/strong&gt;, &lt;strong&gt;Populations&lt;/strong&gt;, &lt;strong&gt;Groups&lt;/strong&gt;, and &lt;strong&gt;Admin Roles&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;An &lt;a href=&quot;https://docs.pingidentity.com/pingone/settings/p1_environments.html&quot;&gt;&lt;strong&gt;Environment&lt;/strong&gt;&lt;/a&gt; is a subdivision of your instance of PingOne. It contains the core resources you’ll use to build your IAM services, including your Users, the Applications they’ll single-sign-on (SSO) into, and the Services and Policies you’ll use to secure the access activities of those Users.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;https://docs.pingidentity.com/pingone/directory/p1_populations.html&quot;&gt;&lt;strong&gt;Population&lt;/strong&gt;&lt;/a&gt; is a unique administrative set of Users and Groups. Populations contain their own configuration such as their own Password Policies, Branding, default Identity Provider, and/or “alternative identifiers” used to easily look up a Population in things like REST APIs and DaVinci Flows.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;https://docs.pingidentity.com/pingone/directory/p1_groups.html&quot;&gt;&lt;strong&gt;Group&lt;/strong&gt;&lt;/a&gt; is a collection of Users. Groups can both be &lt;a href=&quot;https://docs.pingidentity.com/pingone/directory/p1_groups_vs_populations.html#p1-static-dynamic-groups&quot;&gt;assigned dynamically and statically&lt;/a&gt; and can be &lt;a href=&quot;https://docs.pingidentity.com/pingone/directory/p1_groups_vs_populations.html#p1-nested-groups&quot;&gt;nested inside one another&lt;/a&gt;. Groups are used as a way to assign permissions and membership for Users, including Application access and Admin Roles.&lt;/p&gt;
&lt;p&gt;Groups can be created one of two ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;External Groups&lt;/strong&gt; come from an external connection, either by an Identity Provider (IdP) or LDAP Gateway. They are provisioned either via just-in-time or through synchronization and cannot be modified manually in PingOne.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Internal Groups&lt;/strong&gt; are created manually within the PingOne portal like a Population and are a construct represented entirely in PingOne.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A &lt;a href=&quot;https://docs.pingidentity.com/pingone/directory/p1_roles.html#built-in-roles-tab&quot;&gt;&lt;strong&gt;Admin Role&lt;/strong&gt;&lt;/a&gt; is a collection of permissions that you can assign to Users and Groups. Admin Roles allow Users (and Users within the Groups) to interact with the PingOne platform at a higher level of privilege, such as managing the Users within a particular Population. &lt;strong&gt;Only Users and static Groups can be assigned Admin Roles.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;When to Create Multiple Environments&lt;/h2&gt;
&lt;p&gt;A popular use case of Environments is for building a promotion pipeline (e.g. development/staging/production/etc.), but additionally &lt;strong&gt;Environments are subdivided from one another in data and configuration&lt;/strong&gt; - meaning that while they share the same hosting resources they do not share the same PingOne Service Selection (e.g. one Environment can have PingOne MFA while another can have PingID for multi-factor), Policy Configuration (e.g. password policies, risk policies, mfa policies, DaVinci policies, etc.), Branding (e.g. theming, forms, localization), and user data (e.g. Populations/Groups/Users and their user information).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Environments are great when you have requirements for Users, Applications, and Policies that strongly differ from one another.&lt;/strong&gt; Let’s look at an example of managing Employees vs Customers:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Requirement&lt;/th&gt;&lt;th&gt;Employee&lt;/th&gt;&lt;th&gt;Customer&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Multi-Factor Authentication&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Desktop and Mobile Authenticator&lt;/td&gt;&lt;td&gt;Passkeys and Biometric&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Risk Profiles&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;High Risk for New Device&lt;/td&gt;&lt;td&gt;Low Risk for New Device&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Localization&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;English-Only (US Office)&lt;/td&gt;&lt;td&gt;English, Spanish, and French Support&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;User Onboarding&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Provisioning from an on-premise Directory&lt;/td&gt;&lt;td&gt;Social Login with Google and Facebook with progressive profiling&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The same can be said with business relationships. Perhaps you have a grouping of Suppliers who interact with your internal services - if so, it’s likely you’ll be securing and provisioning them in a way more similar to your internal (employee) population. On the other hand, if you are working with Distributors that are then interacting with end-customers, those individuals may be purchasing your products more similarly to an external (customer) population. These B2B2E and B2B2C relationships require drastically different interactions, and as such may benefit from separated Environments.&lt;/p&gt;
&lt;h2&gt;Populations Vs. Groups&lt;/h2&gt;
&lt;p&gt;At a first glance, Populations and Groups look kind of similar. However, they differ in some meaningful ways, namely:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Users must be part of a Population. Groups do not.&lt;/li&gt;
&lt;li&gt;Users and Groups can be a part of ONLY ONE Population. Users and Groups inside a Population can only be a part of Groups in that Population.&lt;/li&gt;
&lt;li&gt;Users and Groups can be a part of MORE THAN ONE Group.&lt;/li&gt;
&lt;li&gt;Groups can have Admin Roles assigned to them, Populations cannot.&lt;/li&gt;
&lt;li&gt;Admin Roles can be scoped to Populations, but not to Groups.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Think of &lt;strong&gt;Populations as a representative set of Users&lt;/strong&gt; and &lt;strong&gt;Groups as a categorization of Users&lt;/strong&gt;. In the real world:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Population could be a Business Partner, while a Group inside that population could be one of their Departments.&lt;/li&gt;
&lt;li&gt;If you had a shared Application (let’s call it &lt;em&gt;Partner Hub&lt;/em&gt;) that all of your Business Partners use, a Group that spans multiple Populations could dynamically contain the Users that need access to that app.&lt;/li&gt;
&lt;li&gt;If you had Support personnel that aided your Business Partners, they could be in a group that has Admin Roles tied to the different Populations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Universal vs Segmented Identity&lt;/h2&gt;
&lt;p&gt;Depending on your business case your users may require a &lt;strong&gt;Universal Identity&lt;/strong&gt; or a &lt;strong&gt;Segmented Identity&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Universal Identity&lt;/strong&gt; is where &lt;strong&gt;one Identity has one User account for all Services&lt;/strong&gt;. This approach is advantageous in tightly-coupled partnerships such as a family of brands, a sports conference/league, a dealership, or a franchise. Universal Identities create seamless login experiences and greatly reduce your identity management footprint.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Segmented Identity&lt;/strong&gt; is where &lt;strong&gt;one Identity has multiple User accounts for different Services&lt;/strong&gt;.This approach is advantageous in distinct relationships such as a supplier, branded enterprise application, an affiliate, or a distributor. Segmented Identities allow finer-grained control and administrative delegation into different levels of secure Applications and Users.&lt;/p&gt;
&lt;p&gt;Using Environments, Populations, and Groups, PingOne enables us to create both Universal and Segmented Identities. Consider the following table when setting up PingOne:&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Identity Type / Service Type&lt;/th&gt;&lt;th&gt;Separated Services&lt;/th&gt;&lt;th&gt;Interconnected Services&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Segmented Identity&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Different&lt;/strong&gt; Environments, Populations, and Groups&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Same&lt;/strong&gt; Environment and Group, &lt;strong&gt;Different&lt;/strong&gt; Populations&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Universal Identity&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Same&lt;/strong&gt; Environment and Population, &lt;strong&gt;Different&lt;/strong&gt; Groups&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Same&lt;/strong&gt; Environment, Population, and Group&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Given the top-level “unique-set” structure of Environments and Populations, PingOne is best suited for Segmented Identity at scale. Please review the &lt;a href=&quot;https://docs.pingidentity.com/pingone/getting_started_with_pingone/p1_platform_limits.html&quot;&gt;Standard Platform Limits&lt;/a&gt; to understand what approach fits your implementation and size.&lt;/p&gt;
&lt;h1&gt;Delegated Administration&lt;/h1&gt;
&lt;p&gt;Now that we have a grasp on the different segmentations of identity and configurations within PingOne, let’s set up our instance to take advantage of it.&lt;/p&gt;
&lt;p&gt;Our example in this guide will start with an out-of-the-box instance of PingOne. Feel free to work from an existing instance - our work here won’t override any of what you’ve done already.&lt;/p&gt;
&lt;p&gt;Your PingOne instance comes pre-loaded with a dedicated &lt;strong&gt;Administrators Environment&lt;/strong&gt;. This environment is where your “super-user” account lives and where you’ll add in most of your Administrators. Let’s leave this Environment alone for now while we set up some basic use-cases.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the PingOne Environments list&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1246&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-list.Dc-UBGCo_1pGnG9.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-list.Dc-UBGCo_ZAv0AT.webp 640w, https://developer.pingidentity.com/blog/_astro/p1-env-list.Dc-UBGCo_ZcSWoc.webp 750w, https://developer.pingidentity.com/blog/_astro/p1-env-list.Dc-UBGCo_ZLoEDd.webp 828w, https://developer.pingidentity.com/blog/_astro/p1-env-list.Dc-UBGCo_1D2Uhv.webp 1080w, https://developer.pingidentity.com/blog/_astro/p1-env-list.Dc-UBGCo_zqx2o.webp 1280w, https://developer.pingidentity.com/blog/_astro/p1-env-list.Dc-UBGCo_qrgg8.webp 1668w, https://developer.pingidentity.com/blog/_astro/p1-env-list.Dc-UBGCo_1pGnG9.webp 1999w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Your PingOne Instance and Administrators Environment&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Example Setup&lt;/h2&gt;
&lt;p&gt;To start, we are going to configure an example for each of the 4 different Service and Identity scenarios we defined in the prior section. To do so, let’s set up two Environments with some Populations, Groups, and Users in them.&lt;/p&gt;
&lt;p&gt;From your Environments View, select the plus “+” icon at the top of the page (or the “Create Environment” button in the “Manage Environments” modal if it’s still on your page).&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot with arrows pointing to the &amp;quot;Create Environment&amp;quot; buttons&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1232&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/p1-create-env.BI-4wtp4_Z1vn2px.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/p1-create-env.BI-4wtp4_G0kFa.webp 640w, https://developer.pingidentity.com/blog/_astro/p1-create-env.BI-4wtp4_2x2hVc.webp 750w, https://developer.pingidentity.com/blog/_astro/p1-create-env.BI-4wtp4_28aG2n.webp 828w, https://developer.pingidentity.com/blog/_astro/p1-create-env.BI-4wtp4_JYG4r.webp 1080w, https://developer.pingidentity.com/blog/_astro/p1-create-env.BI-4wtp4_ZWages.webp 1280w, https://developer.pingidentity.com/blog/_astro/p1-create-env.BI-4wtp4_Z25xrrD.webp 1668w, https://developer.pingidentity.com/blog/_astro/p1-create-env.BI-4wtp4_Z1vn2px.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Creating an Environment&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Creating The Customer Environment&lt;/h3&gt;
&lt;p&gt;First off, let’s create the Customer Environment. On the next page, select the “Customer solution” option. This will come pre-bundled with a variety of services that are common for CIAM deployments, such as Single-Sign-On, Multi-Factor Authentication (PingOne MFA), Risk Profiling (PingOne Protect), Identity Verification (PingOne Verify), and Orchestration (PingOne DaVinci).&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of step 1 in creating an Environment in which the Customer solution has been selected&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-customer.DF5t8wto_Z1DWwrc.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-customer.DF5t8wto_1en4Yl.webp 640w, https://developer.pingidentity.com/blog/_astro/p1-env-customer.DF5t8wto_Z1MQNSj.webp 750w, https://developer.pingidentity.com/blog/_astro/p1-env-customer.DF5t8wto_Z12to0z.webp 828w, https://developer.pingidentity.com/blog/_astro/p1-env-customer.DF5t8wto_Z2uOsk2.webp 1080w, https://developer.pingidentity.com/blog/_astro/p1-env-customer.DF5t8wto_Z29HuYC.webp 1280w, https://developer.pingidentity.com/blog/_astro/p1-env-customer.DF5t8wto_Z2qMSfu.webp 1668w, https://developer.pingidentity.com/blog/_astro/p1-env-customer.DF5t8wto_Z1DWwrc.webp 1999w&quot; /&gt;&amp;gt; &lt;img alt=&quot;A screenshot of step 2 in creating an Environment in which the listed solutions for a Customer prebuild is listed&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-customer-services.BWQIOTyT_2ojNnN.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-customer-services.BWQIOTyT_pRmFH.webp 640w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-services.BWQIOTyT_2hiHLX.webp 750w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-services.BWQIOTyT_ZAkWNP.webp 828w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-services.BWQIOTyT_ZkMcnK.webp 1080w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-services.BWQIOTyT_Z20t3W7.webp 1280w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-services.BWQIOTyT_ZeklfM.webp 1668w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-services.BWQIOTyT_2ojNnN.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;The Environment Type and Services&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In Step 3, where you set the deployment options, input the following values and select “Finish”:&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Environment Name&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Customer Example&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;A testing Environment containing sample flows, policies, and users.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Environment Type&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Sandbox&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Generate sample populations and users in this environment&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;True (checked)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Region&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;The closest region to you. I’m using North America (US)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;The default license that comes with your instance.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Include a solution designer to easily design and test experiences&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;True (checked)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Choose your Industry&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Default&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of step 3 in creating a Customer Environment in which the details provided by the above table are entered&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-customer-details.xDWO0SLk_Z27wQIm.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-customer-details.xDWO0SLk_Z1rI3df.webp 640w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-details.xDWO0SLk_2r4Lt2.webp 750w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-details.xDWO0SLk_Z1DenIM.webp 828w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-details.xDWO0SLk_Z1yoPiK.webp 1080w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-details.xDWO0SLk_ZyEsU2.webp 1280w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-details.xDWO0SLk_2lpze2.webp 1668w, https://developer.pingidentity.com/blog/_astro/p1-env-customer-details.xDWO0SLk_Z27wQIm.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;The Environment Deployment Options&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While this Environment is deploying, let’s create the Workforce Environment.&lt;/p&gt;
&lt;h3&gt;Creating the Workforce Environment&lt;/h3&gt;
&lt;p&gt;Head back to the Environments view by clicking the Home icon or your Organization name in the top tab on your page just to the right of the Ping Identity logo. Mine is that &lt;code&gt;internal_davidgwizdala&lt;/code&gt; text you saw earlier.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the home icon and organization listing&quot; loading=&quot;lazy&quot; width=&quot;1084&quot; height=&quot;92&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/home.BqkWWMMw_2cML6r.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/home.BqkWWMMw_gi8Yx.webp 640w, https://developer.pingidentity.com/blog/_astro/home.BqkWWMMw_Z2hJven.webp 750w, https://developer.pingidentity.com/blog/_astro/home.BqkWWMMw_2npjzN.webp 828w, https://developer.pingidentity.com/blog/_astro/home.BqkWWMMw_2cwukG.webp 1080w, https://developer.pingidentity.com/blog/_astro/home.BqkWWMMw_2cML6r.webp 1084w&quot; /&gt;&amp;gt;
&lt;em&gt;Heading Home&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Back at the Environments view, create another new Environment like you did last time but rather than selecting “Customer solution” we’ll be selecting “Workforce solution” instead. You’ll notice that the services deployed will be slightly different, most notably that PingOne MFA has been replaced by PingID and PingOne Verify has been removed.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of step 1 in creating an Environment in which the Workforce solution has been selected&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-workforce.BB91qOnj_ZlRvGr.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-workforce.BB91qOnj_1SMU1o.webp 640w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce.BB91qOnj_2lcFJV.webp 750w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce.BB91qOnj_2gq9ej.webp 828w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce.BB91qOnj_Z12aQtj.webp 1080w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce.BB91qOnj_Z2uzRrA.webp 1280w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce.BB91qOnj_1MeiVs.webp 1668w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce.BB91qOnj_ZlRvGr.webp 1999w&quot; /&gt;&amp;gt;&lt;img alt=&quot;A screenshot of step 2 in creating an Environment in which the listed solutions for a Workforce prebuild is listed&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-workforce-services.9Q0TQIGq_2fKja3.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-workforce-services.9Q0TQIGq_Ya4B0.webp 640w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-services.9Q0TQIGq_Ey7dX.webp 750w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-services.9Q0TQIGq_ZmTwhR.webp 828w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-services.9Q0TQIGq_Z130UpT.webp 1080w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-services.9Q0TQIGq_1blzp5.webp 1280w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-services.9Q0TQIGq_Z24re4Q.webp 1668w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-services.9Q0TQIGq_2fKja3.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;The Workforce Environment Type and Services&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In Step 3, where you set the deployment options, input the following values and select “Finish”:&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Environment Name&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Workforce Example&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;A testing Workforce Environment containing sample flows, policies, and users.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Environment Type&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Sandbox&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Generate sample populations and users in this environment&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;True (checked)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Region&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;The closest region to you. I’m using North America (US)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;The default license that comes with your instance.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Include a solution designer to easily design and test experiences&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;True (checked)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Choose your Industry&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Default&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of step 3 in creating a Workforce Environment in which the details provided by the above table are entered&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-workforce-details.CLjuTQ3I_Z2uGbXC.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/p1-env-workforce-details.CLjuTQ3I_Z3LMIS.webp 640w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-details.CLjuTQ3I_1MDxmn.webp 750w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-details.CLjuTQ3I_Z1508eq.webp 828w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-details.CLjuTQ3I_Z9B3Vf.webp 1080w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-details.CLjuTQ3I_Z1OhUuB.webp 1280w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-details.CLjuTQ3I_Z39cNh.webp 1668w, https://developer.pingidentity.com/blog/_astro/p1-env-workforce-details.CLjuTQ3I_Z2uGbXC.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Workforce Environment Deployment Options&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now that we’ve created some example Environments to play with, let’s see what samples we are starting with.&lt;/p&gt;
&lt;h3&gt;Reviewing the Setup&lt;/h3&gt;
&lt;p&gt;After setup is complete for both your Customer and Workforce Environments, you’ll see that the steps you’ve taken have set up a few things for you. Click into each of the Environments to check for the following samples:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Under Directory → Users, a series of Users have been added to your Environment.&lt;/li&gt;
&lt;li&gt;Under Directory → Groups, two Groups have been added: &lt;strong&gt;Sample Group&lt;/strong&gt; and &lt;strong&gt;Another Sample Group&lt;/strong&gt;. If you click into the Group Details (by clicking the Group) you’ll see that Sample Group is not tied to any Population while Another Sample Group has been tied to the More Sample Users Population.&lt;/li&gt;
&lt;li&gt;Under Directory → Populations, you’ll have been given two Populations: &lt;strong&gt;Sample Users&lt;/strong&gt; and &lt;strong&gt;More Sample Users&lt;/strong&gt;. These Populations come pre-loaded with their Users and, in the case of the More Sample Users Population, their own Group.&lt;/li&gt;
&lt;li&gt;Under Applications → Applications, you’ll see that you have some Applications already created:
&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;Getting Started Application&lt;/strong&gt;, which is the example application you saw created in the “Getting Started” tab of your Environment and is what is attached to the Solutions Designer you enabled earlier.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;PingOne Admin Console&lt;/strong&gt;, which is where your administrators &lt;strong&gt;created in this Environment&lt;/strong&gt; will go to manage configuration and identity data for this Environment.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;PingOne Application Portal&lt;/strong&gt;, which is where your Users can go to access their applications (think of this as an Application Dock)&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;PingOne DaVinci Connection&lt;/strong&gt;, which links the Environment to orchestration in PingOne&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;PingOne Self-Service - MyAccount&lt;/strong&gt;, which is where your Users can go to manage their own profile details, passwords, and MFA devices.&lt;/li&gt;
&lt;li&gt;Under the Workforce Environment, you’ll also have the &lt;strong&gt;PingID Desktop&lt;/strong&gt; and &lt;strong&gt;PingID Mobile&lt;/strong&gt; applications, which allow your workforce to use the PingID authenticator for Multi-factor authentication. We’ll leave these be for the sake of this example.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With this setup, we have a working demonstration of both how Customers and Employees can interact with PingOne. Now let’s delegate some permissions to it.&lt;/p&gt;
&lt;p&gt;Since this guide is focused on Delegated Administration, we’re going to specifically use the &lt;strong&gt;PingOne Admin Console&lt;/strong&gt; Application. Copy and save the Admin Console URL from the &lt;strong&gt;Administrators Environment&lt;/strong&gt;, either from the Applications page, the Environment Properties (Settings → Environment Properties), or Environment Summary tab (on the Environments List page).&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the home page url found for the Environment&apos;s admin console&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/homepage-url.DtF2PNPQ_Z2ogAb4.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/homepage-url.DtF2PNPQ_Z1L80Pu.webp 640w, https://developer.pingidentity.com/blog/_astro/homepage-url.DtF2PNPQ_Z1XuKzo.webp 750w, https://developer.pingidentity.com/blog/_astro/homepage-url.DtF2PNPQ_ZSK4wj.webp 828w, https://developer.pingidentity.com/blog/_astro/homepage-url.DtF2PNPQ_ZBmDK.webp 1080w, https://developer.pingidentity.com/blog/_astro/homepage-url.DtF2PNPQ_1TeM0o.webp 1280w, https://developer.pingidentity.com/blog/_astro/homepage-url.DtF2PNPQ_mC3uW.webp 1668w, https://developer.pingidentity.com/blog/_astro/homepage-url.DtF2PNPQ_Z2ogAb4.webp 1999w&quot; /&gt;&amp;gt;&lt;img alt=&quot;A screenshot of the console url found under the Environment properties&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/console-url.DGy5n396_2pdQr1.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/console-url.DGy5n396_Z5mRN9.webp 640w, https://developer.pingidentity.com/blog/_astro/console-url.DGy5n396_ieaoy.webp 750w, https://developer.pingidentity.com/blog/_astro/console-url.DGy5n396_Z1mcuvk.webp 828w, https://developer.pingidentity.com/blog/_astro/console-url.DGy5n396_Z26KbPr.webp 1080w, https://developer.pingidentity.com/blog/_astro/console-url.DGy5n396_N6vpQ.webp 1280w, https://developer.pingidentity.com/blog/_astro/console-url.DGy5n396_Z9srVB.webp 1668w, https://developer.pingidentity.com/blog/_astro/console-url.DGy5n396_2pdQr1.webp 1999w&quot; /&gt;&amp;gt;&lt;img alt=&quot;A screenshot of the console login url found on the Environment list page&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/consolelogin-url.Bsk6bdcB_1jsdrp.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/consolelogin-url.Bsk6bdcB_1i4IwC.webp 640w, https://developer.pingidentity.com/blog/_astro/consolelogin-url.Bsk6bdcB_1Jtuga.webp 750w, https://developer.pingidentity.com/blog/_astro/consolelogin-url.Bsk6bdcB_1EGWJx.webp 828w, https://developer.pingidentity.com/blog/_astro/consolelogin-url.Bsk6bdcB_D9REx.webp 1080w, https://developer.pingidentity.com/blog/_astro/consolelogin-url.Bsk6bdcB_ZOf8iJ.webp 1280w, https://developer.pingidentity.com/blog/_astro/consolelogin-url.Bsk6bdcB_Z1BC5IC.webp 1668w, https://developer.pingidentity.com/blog/_astro/consolelogin-url.Bsk6bdcB_1jsdrp.webp 1999w&quot; /&gt;&amp;gt;&lt;br /&gt;
&lt;em&gt;Finding the Admin Console URL&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Creating the Delegated Administrators&lt;/h3&gt;
&lt;p&gt;Remember the Administrators Environment we mentioned earlier? This Environment is the hub where we’ll be assigning permissions for Users to Environments, Populations, and Applications.&lt;/p&gt;
&lt;p&gt;Select the Administrators Environment from your Environments list, copying the Self Service URL on the details tab before entering, and then click on Directory → Users.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the admin environment self-service url found for the Environment&apos;s admin console&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/self-service-url.DkAnHk___1txmo4.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/self-service-url.DkAnHk___1soWRN.webp 640w, https://developer.pingidentity.com/blog/_astro/self-service-url.DkAnHk___1TNIBl.webp 750w, https://developer.pingidentity.com/blog/_astro/self-service-url.DkAnHk___1P2c5I.webp 828w, https://developer.pingidentity.com/blog/_astro/self-service-url.DkAnHk___Nf1Bc.webp 1080w, https://developer.pingidentity.com/blog/_astro/self-service-url.DkAnHk___ZE9Ym5.webp 1280w, https://developer.pingidentity.com/blog/_astro/self-service-url.DkAnHk___Z1rwVLX.webp 1668w, https://developer.pingidentity.com/blog/_astro/self-service-url.DkAnHk___1txmo4.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Saving the Admin Environment Self-Service URL for Later&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We are going to be creating a couple different users to administer the Environments we created. Click the Plus “+” icon next to the Users header, enter in each of the following User details, and hit “Save” when done. You’ll be creating two users in total.&lt;/p&gt;


















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;User 1&lt;/th&gt;&lt;th&gt;User 2&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Given Name&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Customer&lt;/td&gt;&lt;td&gt;Workforce&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Family Name&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Admin&lt;/td&gt;&lt;td&gt;Admin&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Username&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;customerAdmin&lt;/td&gt;&lt;td&gt;workforceAdmin&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Email&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;An email you can access&lt;/td&gt;&lt;td&gt;An email you can access&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Require Email to be Verified&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;True (checked)&lt;/td&gt;&lt;td&gt;True (checked)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Population&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Administrators Population&lt;/td&gt;&lt;td&gt;Administrators Population&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Authoritative Identity Provider&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;PingOne (default)&lt;/td&gt;&lt;td&gt;PingOne (default)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Password&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Ch4ngeIt!&lt;/td&gt;&lt;td&gt;Ch4ngeIt!&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Email Addresses for all admin accounts (i.e. accounts with delegated roles) &lt;strong&gt;must be verified&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Next, let’s verify these accounts so that we can perform delegated administration. In an incognito, guest, or separate browser window, go to the Self-Service URL for the Admin Environment that you copied earlier. Enter in the username and password of your administrators - you’ll then be presented with a change password prompt.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the user flow in which the admin is required to change their password&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-change-password.DNcttpEC_11M2NE.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-change-password.DNcttpEC_Zixaxl.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-change-password.DNcttpEC_ZIvsCg.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-change-password.DNcttpEC_1Y4XKF.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-change-password.DNcttpEC_21CCjY.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-change-password.DNcttpEC_FVRFk.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-change-password.DNcttpEC_Z1TSfxv.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-change-password.DNcttpEC_11M2NE.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;The Change Password Prompt&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Enter in the current password and then a new password of your choice. You’ll then be requested to enter in the verification code that was sent to your User when you created their account.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the user flow in which the admin is required to enter in their verification code they received via email&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-email-verification.IJ0Q5mze_Z1h68fi.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-email-verification.IJ0Q5mze_Z1YrI9E.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-email-verification.IJ0Q5mze_Z81n3o.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-email-verification.IJ0Q5mze_24w59J.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-email-verification.IJ0Q5mze_13XYM5.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-email-verification.IJ0Q5mze_ZAGQLh.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-email-verification.IJ0Q5mze_1aqPU3.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-email-verification.IJ0Q5mze_Z1h68fi.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Entering in the Verification Code&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Repeat the above steps for the Workforce Administrator. When you return to your Administrator Environment and check the Users, you’ll see that the status of their email address is “Verified”.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the admin page indicating the user&apos;s email has been verified&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-verified.DbBhVhfj_Z1fOHxI.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-verified.DbBhVhfj_ZiF4RI.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-verified.DbBhVhfj_ZOF9hR.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-verified.DbBhVhfj_Z1vGzfe.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-verified.DbBhVhfj_Z1W22UC.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-verified.DbBhVhfj_ZyjfJC.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-verified.DbBhVhfj_Z2srYo9.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-verified.DbBhVhfj_Z1fOHxI.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;The Verified Admin Email Address&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Delegating Administration by User&lt;/h2&gt;
&lt;p&gt;Right now, these Administrators are a part of the Administrators Environment but don’t have any specific permissions. Remember the Console URL we copied earlier?  Paste it into its own separate browser (or guest account) window and try to log in as either of the admins you just created. You’ll encounter an “Incorrect username or password. Please try again.” message.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the user flow in which the admin fails a login attempt to an environment they don&apos;t have permission to&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-failed-login.B5dcc9O8_TuBS.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-failed-login.B5dcc9O8_Z1BaYV5.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-failed-login.B5dcc9O8_ZV67VN.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-failed-login.B5dcc9O8_UPkC3.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-failed-login.B5dcc9O8_Z1HBHWh.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-failed-login.B5dcc9O8_Znu13M.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-failed-login.B5dcc9O8_Z1sMmvu.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-failed-login.B5dcc9O8_TuBS.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Trying to Log In without Permissions&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now, let’s give these users some permissions. Starting with the Customer Admin, select the “Roles” tab in their user profile and then “Grant Roles”.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the admin portal where the grant roles option has been highlighted for the customerAdmin user&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/grant-user-role.Bs6oEEwO_1n1WMy.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/grant-user-role.Bs6oEEwO_1kl5WQ.webp 640w, https://developer.pingidentity.com/blog/_astro/grant-user-role.Bs6oEEwO_Z1GSMTN.webp 750w, https://developer.pingidentity.com/blog/_astro/grant-user-role.Bs6oEEwO_ZVvn24.webp 828w, https://developer.pingidentity.com/blog/_astro/grant-user-role.Bs6oEEwO_wa1TI.webp 1080w, https://developer.pingidentity.com/blog/_astro/grant-user-role.Bs6oEEwO_RgYf8.webp 1280w, https://developer.pingidentity.com/blog/_astro/grant-user-role.Bs6oEEwO_AbAYg.webp 1668w, https://developer.pingidentity.com/blog/_astro/grant-user-role.Bs6oEEwO_1n1WMy.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Granting Admin Roles to a User&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;From here, you’ll see a wide variety of permissions available to assign. Let’s start at the highest level and work our way down.&lt;/p&gt;
&lt;p&gt;First, set your Customer Admin to be an Environment Admin of the Customer Example Environment and hit Save. This gives your admin permission to View, Create, Update, and Delete configuration within the Environment. More details can be found in the info “i” icon next to the title, but for all intents and purposes &lt;strong&gt;think of the Environment Admin the manager of Environment Configuration.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the admin portal where the Environment Admin permission to the Customer Environment is being granted to the customerAdmin&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/grant-user-role-env.BIkc4Y5p_GxXtJ.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/grant-user-role-env.BIkc4Y5p_Na6Wv.webp 640w, https://developer.pingidentity.com/blog/_astro/grant-user-role-env.BIkc4Y5p_ZFWCet.webp 750w, https://developer.pingidentity.com/blog/_astro/grant-user-role-env.BIkc4Y5p_Z21ON55.webp 828w, https://developer.pingidentity.com/blog/_astro/grant-user-role-env.BIkc4Y5p_Z1OdCHC.webp 1080w, https://developer.pingidentity.com/blog/_astro/grant-user-role-env.BIkc4Y5p_1LJLwX.webp 1280w, https://developer.pingidentity.com/blog/_astro/grant-user-role-env.BIkc4Y5p_1oLsbP.webp 1668w, https://developer.pingidentity.com/blog/_astro/grant-user-role-env.BIkc4Y5p_GxXtJ.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Adding Environment Permissions&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you try logging in again, you’ll see that you’re now prompted with MFA enrollment (a default setting for Admin Users). Go ahead and enroll in a device of your choice.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the user flow where the admin has to enroll an mfa device&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-enroll-mfa.DywuSoRq_Z2nFgAX.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-enroll-mfa.DywuSoRq_1YNH1d.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-enroll-mfa.DywuSoRq_2rdsJK.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-enroll-mfa.DywuSoRq_2mqVe8.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-enroll-mfa.DywuSoRq_21dwq6.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-enroll-mfa.DywuSoRq_xNvrO.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-enroll-mfa.DywuSoRq_ZeyqX4.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-enroll-mfa.DywuSoRq_Z2nFgAX.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Enrolling MFA as an Admin&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You’ll see that your Customer Administrator has access to configuration details such as Monitoring, Applications, Policies, Roles, and Experience but doesn’t have the capability to see or manage Users directly.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the admin portal for the Customer environment with the granted roles to the customerAdmin. In this page, the admin can see a Group but can&apos;t edit it nor can they see the Users in the Group&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-env-view.Dx0GdKBl_2dhMj6.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-env-view.Dx0GdKBl_ZPQeQy.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-env-view.Dx0GdKBl_Z1mQjgH.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-env-view.Dx0GdKBl_Z23RJe4.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-env-view.Dx0GdKBl_1w5rVc.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-env-view.Dx0GdKBl_Z2anSGJ.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-env-view.Dx0GdKBl_10EvsF.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-env-view.Dx0GdKBl_2dhMj6.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;The Environment Admin’s Capabilities&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Most Admin Roles give us the capability to assign by Organization or Environment (see DaVinci Admin, Application Admin), but let’s get a bit more granular. Back in the Administrators Environment view, update the Customer Admin so that they &lt;strong&gt;don’t&lt;/strong&gt; have the Environment Administrator Admin Role and &lt;strong&gt;do&lt;/strong&gt; have the Identity Data Admin Role specifically for the “Sample Users” Population. To do that, select the Identity Data Admin Role, 1) click the Filter icon (next to the checkbox) and 2) select the Sample Users Population. When you’re done, you should see “Limited Access” next to the Population and then the specific population after saving in the User summary.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the admin portal where the identity data admin granted permission is filtered to a specific Population. The steps taken to the filter are highlighted.&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/grant-user-role-filter.MRMsi55a_Zy1v05.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/grant-user-role-filter.MRMsi55a_203csw.webp 640w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter.MRMsi55a_21HlQY.webp 750w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter.MRMsi55a_1v9Qnf.webp 828w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter.MRMsi55a_6AvtQ.webp 1080w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter.MRMsi55a_2kCeYO.webp 1280w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter.MRMsi55a_GLW42.webp 1668w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter.MRMsi55a_Zy1v05.webp 1999w&quot; /&gt;&amp;gt; &lt;img alt=&quot;A screenshot of the admin portal where the filter has been applied&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-applied.iDMM_Wdc_lYrTv.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-applied.iDMM_Wdc_MoyAO.webp 640w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-applied.iDMM_Wdc_10rbwD.webp 750w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-applied.iDMM_Wdc_WmVls.webp 828w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-applied.iDMM_Wdc_Z1naoD8.webp 1080w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-applied.iDMM_Wdc_1sW8aQ.webp 1280w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-applied.iDMM_Wdc_275PyD.webp 1668w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-applied.iDMM_Wdc_lYrTv.webp 1999w&quot; /&gt;&amp;gt; &lt;img alt=&quot;A screenshot of the admin portal where the grant has been updated and the summary is shown for the customerAdmin&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-summary.ComP_swZ_1oH6J8.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-summary.ComP_swZ_aqmVA.webp 640w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-summary.ComP_swZ_nsYRp.webp 750w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-summary.ComP_swZ_koJGe.webp 828w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-summary.ComP_swZ_ZkrJNv.webp 1080w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-summary.ComP_swZ_2vEM0t.webp 1280w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-summary.ComP_swZ_Z1UnDpF.webp 1668w, https://developer.pingidentity.com/blog/_astro/grant-user-role-filter-summary.ComP_swZ_1oH6J8.webp 1999w&quot; /&gt;&amp;gt;&lt;br /&gt;
&lt;em&gt;Limiting Access to the Sample Users Population&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now go back to your customerAdmin’s dashboard. After refreshing the page, you’ll see that most of your access has been revoked to Read-Only but now you can manage the Users within the Sample Population.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the customerAdmin&apos;s new permissions to access the Sample Users Population&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-view-pop.DejbOj08_1QCfPw.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-view-pop.DejbOj08_Z2vTNm.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-view-pop.DejbOj08_ZyvYdv.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-view-pop.DejbOj08_Z1fxpaR.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-view-pop.DejbOj08_1apUsC.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-view-pop.DejbOj08_Z2w3qaj.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-view-pop.DejbOj08_DYY06.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-view-pop.DejbOj08_1QCfPw.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the customerAdmin&apos;s view into the users under the Sample Users Population&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-view-users.BHFDx1dE_2k7NcY.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-view-users.BHFDx1dE_Z1M3mFJ.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-view-users.BHFDx1dE_Z1kDAWc.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-view-users.BHFDx1dE_Z1pq8sO.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-view-users.BHFDx1dE_1DOsq7.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-view-users.BHFDx1dE_bprrP.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-view-users.BHFDx1dE_ZAWuX3.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-view-users.BHFDx1dE_2k7NcY.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Identity Data Admin View&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Using what you just learned, assign the same Admin Role to your Workforce Admin but with their permission pointed to the Sample Users Population in the Workforce Environment.&lt;/p&gt;
&lt;h2&gt;Delegating Administration by Group&lt;/h2&gt;
&lt;p&gt;So right now we have a User that can manage the Identity Data of one Population in one Environment. Likely, as your organization expands, it’ll become unwieldy (and error-prone) to assign these permissions one at a time. Rather than assigning on a User level, let’s assign to a Group instead.&lt;/p&gt;
&lt;p&gt;Inside the Administrators Environment, under Directory → Groups, select the Plus “+” icon and give it the Group Name “Helpdesk”. This group is going to be able to manage all of the identities for both the customer and workforce environments.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of creating the helpdesk Group within the admin portal&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/create-group.CDJqZMa2_DquKD.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/create-group.CDJqZMa2_1KYCLa.webp 640w, https://developer.pingidentity.com/blog/_astro/create-group.CDJqZMa2_1yBS2g.webp 750w, https://developer.pingidentity.com/blog/_astro/create-group.CDJqZMa2_Z2qOyIA.webp 828w, https://developer.pingidentity.com/blog/_astro/create-group.CDJqZMa2_Z236pvY.webp 1080w, https://developer.pingidentity.com/blog/_astro/create-group.CDJqZMa2_Z8ffQP.webp 1280w, https://developer.pingidentity.com/blog/_astro/create-group.CDJqZMa2_Z1EQYmh.webp 1668w, https://developer.pingidentity.com/blog/_astro/create-group.CDJqZMa2_DquKD.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Creating the Group&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This Group is assigned outside of a Population so we can add any Users that are in this Environment to it. With that in mind, go to the Users tab in the Helpdesk group and select “Add Individually”. We mentioned this before, but to protect from accidental overpermissioning &lt;strong&gt;Administrative Groups Permissions CANNOT be assigned via a filter.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the admin portal where the &amp;quot;Add Individually&amp;quot; button for adding Users to a Group is being selected&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/assign-to-group.CpeMpKgt_ZVnH03.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/assign-to-group.CpeMpKgt_Z1Gkef0.webp 640w, https://developer.pingidentity.com/blog/_astro/assign-to-group.CpeMpKgt_lC0Gh.webp 750w, https://developer.pingidentity.com/blog/_astro/assign-to-group.CpeMpKgt_170qz1.webp 828w, https://developer.pingidentity.com/blog/_astro/assign-to-group.CpeMpKgt_Z1MfCRS.webp 1080w, https://developer.pingidentity.com/blog/_astro/assign-to-group.CpeMpKgt_Z1r8Fxt.webp 1280w, https://developer.pingidentity.com/blog/_astro/assign-to-group.CpeMpKgt_Z1Ie3Nl.webp 1668w, https://developer.pingidentity.com/blog/_astro/assign-to-group.CpeMpKgt_ZVnH03.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;Adding Individual Users to the Helpdesk Group&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On the next screen, select the customerAdmin and the workforceAdmin Users to this Group.&lt;/p&gt;
&lt;p&gt;Next, go to the Roles Tab and select “Grant Roles”. This screen will look very familiar to you: it’s the same screen we used when assigning Admin Roles to Users!&lt;/p&gt;
&lt;p&gt;This time, let’s give this Group the Identity Data Admin Role across both the Customer Example and Workforce Example Environments.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the admin portal where the Group is being assigned Identity Data Admin roles&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/group-roles.Di3yCDlj_bg7Lo.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/group-roles.Di3yCDlj_Z1CNVNd.webp 640w, https://developer.pingidentity.com/blog/_astro/group-roles.Di3yCDlj_Z1fcSAv.webp 750w, https://developer.pingidentity.com/blog/_astro/group-roles.Di3yCDlj_2axzix.webp 828w, https://developer.pingidentity.com/blog/_astro/group-roles.Di3yCDlj_JtdiR.webp 1080w, https://developer.pingidentity.com/blog/_astro/group-roles.Di3yCDlj_Z1pQdeL.webp 1280w, https://developer.pingidentity.com/blog/_astro/group-roles.Di3yCDlj_Z2nqbBe.webp 1668w, https://developer.pingidentity.com/blog/_astro/group-roles.Di3yCDlj_bg7Lo.webp 1999w&quot; /&gt;&amp;gt;&lt;br /&gt;
&lt;em&gt;Assigning the Identity Data Admin Roles&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When you hit Save, and go back to your customerAdmin or workforceAdmin User, you’ll see that their permissions have been “Granted By Group” to the two Environments.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the admin portal where the admins assigned to the Group have had updated permissions&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/updated-permissions.6fpMpdJo_1SNMk.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/updated-permissions.6fpMpdJo_rPjPW.webp 640w, https://developer.pingidentity.com/blog/_astro/updated-permissions.6fpMpdJo_Z12hpl2.webp 750w, https://developer.pingidentity.com/blog/_astro/updated-permissions.6fpMpdJo_Z2n9AbD.webp 828w, https://developer.pingidentity.com/blog/_astro/updated-permissions.6fpMpdJo_Z2tRMp2.webp 1080w, https://developer.pingidentity.com/blog/_astro/updated-permissions.6fpMpdJo_175BPy.webp 1280w, https://developer.pingidentity.com/blog/_astro/updated-permissions.6fpMpdJo_J7iuq.webp 1668w, https://developer.pingidentity.com/blog/_astro/updated-permissions.6fpMpdJo_1SNMk.webp 1999w&quot; /&gt;&amp;gt;
&lt;em&gt;The User’s Assigned Admin Roles, Direct and Group&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You probably also saw an error message pop up - we’ve just tried to assign the same Admin Role twice to our Admin Users. The highest amount of permission will win here: in this case, the Helpdesk Identity Data Admin Role that grants access to all Users in both Environments.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Go ahead and refresh the admin portal for either your customerAdmin or your workforceAdmin. Not only will you see all 40 users in your current Environment, you’ll additionally be able to navigate into and see the other Environment too.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A screenshot of the customerAdmin being able to see all 40 users from all populations&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-view-all-users.B4tYADZv_ZYa3B8.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-view-all-users.B4tYADZv_Z1Y4HXh.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-view-all-users.B4tYADZv_waEyG.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-view-all-users.B4tYADZv_1n3l9x.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-view-all-users.B4tYADZv_2tqbG1.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-view-all-users.B4tYADZv_mGsSD.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-view-all-users.B4tYADZv_1F6DBy.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-view-all-users.B4tYADZv_ZYa3B8.webp 1999w&quot; /&gt;&amp;gt;
&lt;img alt=&quot;A screenshot of the customerAdmin being able to see multiple environments&quot; loading=&quot;lazy&quot; width=&quot;1999&quot; height=&quot;1239&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/admin-view-environments.D5Fh-5hB_ZQbAP5.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/admin-view-environments.D5Fh-5hB_Zgb6BB.webp 640w, https://developer.pingidentity.com/blog/_astro/admin-view-environments.D5Fh-5hB_Z1rzpJg.webp 750w, https://developer.pingidentity.com/blog/_astro/admin-view-environments.D5Fh-5hB_ZrGr89.webp 828w, https://developer.pingidentity.com/blog/_astro/admin-view-environments.D5Fh-5hB_Zi3zpt.webp 1080w, https://developer.pingidentity.com/blog/_astro/admin-view-environments.D5Fh-5hB_GFLXf.webp 1280w, https://developer.pingidentity.com/blog/_astro/admin-view-environments.D5Fh-5hB_Z1sqiGC.webp 1668w, https://developer.pingidentity.com/blog/_astro/admin-view-environments.D5Fh-5hB_ZQbAP5.webp 1999w&quot; /&gt;&amp;gt;&lt;br /&gt;
&lt;em&gt;The Assigned Group Permissions to the End User&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Putting it to Practice&lt;/h2&gt;
&lt;p&gt;So now you know how to assign Admin Roles to a User and to a Group - let’s try some common combinations that will help you and your Users best interact with the platform. If you want a challenge, try to assign the right roles before reading which options you need.&lt;/p&gt;

  Managing Data about Users
&lt;ul&gt;
&lt;li&gt;Identity Data Admin&lt;/li&gt;
&lt;/ul&gt;


  Creating New Applications and Managing Specific Applications
&lt;ul&gt;
&lt;li&gt;Client App Developer&lt;/li&gt;
&lt;li&gt;Application Admin&lt;/li&gt;
&lt;/ul&gt;


  Defining Authentication Policies and Experiences
&lt;ul&gt;
&lt;li&gt;Environment Admin&lt;/li&gt;
&lt;li&gt;DaVinci Admin&lt;/li&gt;
&lt;/ul&gt;


  User Experience Tester
&lt;ul&gt;
&lt;li&gt;DaVinci Admin&lt;/li&gt;
&lt;li&gt;Identity Data Admin (you need a test user to test a flow!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;Extending Delegated Administration&lt;/h1&gt;
&lt;p&gt;We’ve barely scratched the surface for what Delegated Administration can do within PingOne. To give you some jumping off points, take a look at common ways the Delegated Administration model is extended within the platform.&lt;/p&gt;
&lt;h2&gt;In-Environment Admins (Partner Admins)&lt;/h2&gt;
&lt;p&gt;Remember when we first looked at the Administrators Environment and said that &lt;strong&gt;most&lt;/strong&gt; delegated administrators would be in this environment? Let’s talk through the scenarios in which you may want to &lt;strong&gt;assign delegated administration in a non-admin Environment&lt;/strong&gt; instead. Think of this section as the &lt;strong&gt;Universal Identity&lt;/strong&gt; vs. Segmented Identity (which we did in the last section) scenario.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The delegated administrator utilizes the same policies (e.g. password, MFA, DaVinci, Risk) as the environment or identities that they manage (e.g. service user)&lt;/li&gt;
&lt;li&gt;The delegated administrator requires a distinct login experience (e.g. UI/Branding/Flow) indicative of the partnership or brand they are coming from (e.g. branding director)&lt;/li&gt;
&lt;li&gt;The delegated administrator accesses the same Applications that their users are managing, perhaps with a different authorization policy (e.g. Application manager)&lt;/li&gt;
&lt;li&gt;The delegated administrator Federates from the same IdP as their users (e.g. external helpdesk, business owner)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In these cases (and more!) ensure that your delegated administrators are Grouped in meaningful ways - the more that the Admin Roles are defined at a Group level the easier it will be to track what permissions each of your Users have.&lt;/p&gt;
&lt;h2&gt;Custom Admin Roles&lt;/h2&gt;
&lt;p&gt;While PingOne provides a series of Admin Roles out of the box for you and your administrators to utilize, you can create more catered Admin Role permissions through the use of &lt;a href=&quot;https://docs.pingidentity.com/pingone/directory/p1_custom_role_add.html&quot;&gt;Custom Admin Roles&lt;/a&gt;. These allow you to set CRUD-based permissions on the same set of Administrative Roles we were working with in the other section.&lt;/p&gt;
&lt;p&gt;Since the Documentation contains &lt;a href=&quot;https://docs.pingidentity.com/pingone/directory/p1_custom_roles_scenarios_intro.html&quot;&gt;Custom Admin Role Scenarios&lt;/a&gt; for you to look at, I’m not going to provide a walkthrough here. That being said, combining Custom Admin Roles with your Groups and Users gives you the ultimate level of control across your Environments.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Through this Guide we have:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Learned about the key definitions and differences between structures within PingOne&lt;/li&gt;
&lt;li&gt;Created our own unique example Environments containing these structures&lt;/li&gt;
&lt;li&gt;Delegated administration to external users to manage the different structures.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Additionally, we have looked at ways that Ping extends this capability through the user of assigning Admin Roles within non-Admin Environments (“Partner Admins”) and through the creation of Custom Admin Roles.&lt;/p&gt;
&lt;p&gt;With what you have learned today you should be able to confidently create and assign complex Admin Role relationships for your internal and external administration.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do you have thoughts or questions on this article? Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.1PUsVrVE_Z16vVkj.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Using the new Use Case Library for fast onboarding with PingOne APIs</title><link>https://developer.pingidentity.com/blog/use-case-library-fast-onboarding/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/use-case-library-fast-onboarding/</guid><description>Our redesigned Getting Started and Use Case Library Postman collections help you fast-track your PingOne API journey.</description><pubDate>Wed, 06 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;PingOne APIs pull back the curtain to reveal precisely how PingOne sign-on flows work. From application configuration and user registration to calling the PingOne authorization server to initiate a flow, the APIs show you the detailed request and response data for each step. Whether you want a simple username/password flow, a passwordless flow, or something more intricate, the PingOne APIs are a powerful tool for getting the job done and for understanding how PingOne works.&lt;/p&gt;
&lt;h2&gt;Syncing the Postman collections&lt;/h2&gt;
&lt;p&gt;The PingOne API developer doc library contains two key resources to fast-track your API journey: Getting Started and Use Case Library. The three tasks in Getting Started walk you through the process of getting a PingOne admin access token and using that token to call PingOne APIs.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Getting Started tasks&quot; loading=&quot;lazy&quot; width=&quot;2666&quot; height=&quot;902&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/getting-started-tasks.DsEtVRXp_1jXWjb.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/getting-started-tasks.DsEtVRXp_1INmLt.webp 640w, https://developer.pingidentity.com/blog/_astro/getting-started-tasks.DsEtVRXp_1dwElU.webp 750w, https://developer.pingidentity.com/blog/_astro/getting-started-tasks.DsEtVRXp_Z2MBQv.webp 828w, https://developer.pingidentity.com/blog/_astro/getting-started-tasks.DsEtVRXp_Zt1Ml0.webp 1080w, https://developer.pingidentity.com/blog/_astro/getting-started-tasks.DsEtVRXp_71NLp.webp 1280w, https://developer.pingidentity.com/blog/_astro/getting-started-tasks.DsEtVRXp_kpn8C.webp 1668w, https://developer.pingidentity.com/blog/_astro/getting-started-tasks.DsEtVRXp_ITNWs.webp 2048w, https://developer.pingidentity.com/blog/_astro/getting-started-tasks.DsEtVRXp_Z1zGR8w.webp 2560w, https://developer.pingidentity.com/blog/_astro/getting-started-tasks.DsEtVRXp_1jXWjb.webp 2666w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;First, use the admin console to create the PingOne admin Worker application needed to issue an admin access token that gives you permission to run PingOne APIs. With an admin access token, you can then create a test environment, configure a web application, and define a test user, which are essential resources for running sign-on flows.&lt;/p&gt;
&lt;h2&gt;Exploring the use cases&lt;/h2&gt;
&lt;p&gt;The use cases align with PingOne best practices and give you steps to configure and run a wide range of sign-on flows. Building on the assets you created in Getting Started, the use cases streamline the environment and runtime endpoint configurations. For example, PingOne supports five methods for authenticating the token request. To explore how the flow differs based on token authentication method, the Use Case Library Postman collection contains a simple PUT request that operates on the asset you created in Getting Started to set the app configuration for your chosen method.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Use case environment configuration&quot; loading=&quot;lazy&quot; width=&quot;2748&quot; height=&quot;1212&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/use-case-env-config.CFKb1ZrR_jsaaX.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/use-case-env-config.CFKb1ZrR_1w243X.webp 640w, https://developer.pingidentity.com/blog/_astro/use-case-env-config.CFKb1ZrR_ZlopWu.webp 750w, https://developer.pingidentity.com/blog/_astro/use-case-env-config.CFKb1ZrR_Z2rEaA6.webp 828w, https://developer.pingidentity.com/blog/_astro/use-case-env-config.CFKb1ZrR_Z240L1H.webp 1080w, https://developer.pingidentity.com/blog/_astro/use-case-env-config.CFKb1ZrR_Z2dM9EI.webp 1280w, https://developer.pingidentity.com/blog/_astro/use-case-env-config.CFKb1ZrR_1hVOMv.webp 1668w, https://developer.pingidentity.com/blog/_astro/use-case-env-config.CFKb1ZrR_20dlLa.webp 2048w, https://developer.pingidentity.com/blog/_astro/use-case-env-config.CFKb1ZrR_Zi8kyz.webp 2560w, https://developer.pingidentity.com/blog/_astro/use-case-env-config.CFKb1ZrR_jsaaX.webp 2748w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;In addition, the Postman collection includes runtime endpoints precisely configured to test the flow based on your application settings. You can see the parameters required by the authorization request to initiate the flow based on your application’s configuration, and the properties needed in the token request to return an access token.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Use case test the workflow&quot; loading=&quot;lazy&quot; width=&quot;2622&quot; height=&quot;1306&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/use-case-test-flow.ClRsAkqR_Z1x4nDl.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/use-case-test-flow.ClRsAkqR_Z1of2s0.webp 640w, https://developer.pingidentity.com/blog/_astro/use-case-test-flow.ClRsAkqR_fMtLm.webp 750w, https://developer.pingidentity.com/blog/_astro/use-case-test-flow.ClRsAkqR_Z2i9T40.webp 828w, https://developer.pingidentity.com/blog/_astro/use-case-test-flow.ClRsAkqR_hIuTK.webp 1080w, https://developer.pingidentity.com/blog/_astro/use-case-test-flow.ClRsAkqR_Z1UrGKd.webp 1280w, https://developer.pingidentity.com/blog/_astro/use-case-test-flow.ClRsAkqR_Z1MjOBh.webp 1668w, https://developer.pingidentity.com/blog/_astro/use-case-test-flow.ClRsAkqR_Z1izcnp.webp 2048w, https://developer.pingidentity.com/blog/_astro/use-case-test-flow.ClRsAkqR_1xaDUE.webp 2560w, https://developer.pingidentity.com/blog/_astro/use-case-test-flow.ClRsAkqR_Z1x4nDl.webp 2622w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;The Use Case Library contains a diverse collection of flows, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Device authorization for OAuth-enabled devices, such as smart TVs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consent agreement to obtain a user’s agreement to a policy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Progressive profiling to add data to a user’s profile.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Multifactor authentication flows for greater security.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Passwordless authentication flows.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Proof Key for Code Exchange (PKCE) authentication flows.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DaVinci sign-on and registration flows.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These use cases show you how the configuration and runtime endpoints work together to build the flow. Think of them not only as a hands-on tutorial but also as a starting point for further development and experimentation with PingOne APIs.&lt;/p&gt;
&lt;h2&gt;Let us know what you think&lt;/h2&gt;
&lt;p&gt;Download the Postman collections for your favorite use cases and give it a try. If you have ideas about use cases that we haven’t covered in the updated content, let us know by clicking the feedback icon in the top-right corner of any page.&lt;/p&gt;
&lt;p&gt;Enjoy the ride!&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Try &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/getting-started/&quot;&gt;Getting Started&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Try &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/workflow-library/&quot;&gt;Use Case Library&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Check out the &lt;a href=&quot;https://developer.pingidentity.com/&quot;&gt;Developer Portal&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Check out the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity Developer Community&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.BOqoeYJk_ZRA2Fe.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>App-to-web session transfer: the security perspective</title><link>https://developer.pingidentity.com/blog/app-to-web-session-transfer-the-security-perspective/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/app-to-web-session-transfer-the-security-perspective/</guid><description>Exploring the security implications and enhancements of app-to-web session transfer patterns — examining OAuth risks, device binding strategies, and layered security controls for mobile-to-web SSO.</description><pubDate>Wed, 06 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This post was originally published on &lt;a href=&quot;https://medium.com/@robertotto_31404/app-to-web-session-transfer-the-security-perspective-4c9ede049002&quot;&gt;Medium&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This post is the third in a series; in the first, we discuss the &lt;a href=&quot;https://developer.pingidentity.com/blog/hardening-the-handoff-a-pattern-for-app-to-web-single-sign-on/&quot;&gt;app-to-web session transfer problem&lt;/a&gt; and propose a secure approach using OAuth 2.0 &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9126&quot;&gt;Pushed Authorisation Requests&lt;/a&gt; (PAR). In the second, we walked through a &lt;a href=&quot;https://developer.pingidentity.com/blog/implementing-secure-session-transfer-a-field-guide-using-the-pingone-platform/&quot;&gt;detailed implementation blueprint&lt;/a&gt; using the PingOne platform. This third part will dive specifically into the security implications of this approach and a number of potential enhancements we could consider. &lt;em&gt;Familiarity with the previous two parts is recommended before diving into this one.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Understanding the risks&lt;/h2&gt;
&lt;p&gt;First, a disclaimer. What follows is not exhaustive — nor could it ever be. I urge anyone reading and following this advice to remember that what I’m providing here is certainly not meant to be iron-clad security guidance, however well-reasoned I like to think it may be!&lt;/p&gt;
&lt;p&gt;Nothing that we implement in any scenario should ever be considered 100% secure anyway (not in a world where we have integrated systems with boundaries — not to mention real-life users!) Our challenge is always to do what we can to predict the threats we might face, assess the likelihood and impact of each such risk and then critically assess our proposed solution against both aspects.&lt;/p&gt;
&lt;p&gt;If we consider the pattern end-to-end, we should be able to identify a number of potential weak points where a motivated and skilled adversary could compromise security. The first is not a new or additional risk factor, given that we are using OAuth 2 to secure a native mobile application since it relates to the usage of an access token as a &lt;strong&gt;bearer token&lt;/strong&gt; to allow API access. This implies that the utmost care must be taken within the end-to-end system architecture (but particularly within the client app itself) to protect this access token from unauthorised access or exfiltration.&lt;/p&gt;
&lt;p&gt;We can argue that our PAR-based approach does not create any additional risk of access token exposure beyond what is already present anyway. When our application makes an authenticated call to a backend API, it initiates an HTTPS request and includes the access token in an authorisation header. The PAR approach includes the access token as a POST body parameter over HTTPS to our authorisation server and since we trust the server and the orchestration logic it executes, this should not materially affect our exposure risk.&lt;/p&gt;
&lt;h2&gt;Additional controls&lt;/h2&gt;
&lt;p&gt;We have added a number of additional controls to guard against specific risks, including the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;We use a dedicated session transfer client to initiate the PAR process and intentionally restrict this client’s capabilities with the &lt;em&gt;“web-session-only”&lt;/em&gt; scope. By configuring this client with a meaningless scope, an invalid redirect_URI and by including a PKCE Code Challenge we add a number of layered obstacles to prevent misuse of this client — that is in addition to specific orchestration logic to prevent the successful issue of any access token to this client.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The PAR response contains a &lt;em&gt;request_uri&lt;/em&gt; parameter and this, while inherently single-use and short-lived, does create a potential security risk since it could be exfiltrated and copied to a different device in order to hijack the web session that it creates. We mitigate this risk by further constraining the useful life of the request (via our 10-second &lt;em&gt;exp&lt;/em&gt; claim window) and by performing an IP check ahead of creating the web session.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The missing piece: device binding&lt;/h2&gt;
&lt;p&gt;One significant risk that we have not adequately addressed is that of access token exfiltration followed by a spoofed PAR request from a different client. Given that the access token itself is all that conveys the authenticated user context, we need to ensure that should an attacker obtain a valid access token by some nefarious means, they would not be able to use this token to create a valid web session on a different device.&lt;/p&gt;
&lt;p&gt;Our remaining challenge is thus to implement some mechanism to ensure that PAR request is initiated (and completed) on the same device that obtained the access token initially.&lt;/p&gt;
&lt;p&gt;We should, at this point, look critically at the decision to use IP address as the mechanism for binding the backend and front-end requests. While it is arguably the simplest thing to implement, IP address is typically a rather weak proxy for real device identity since it can be almost trivially replicated via proxy.&lt;/p&gt;
&lt;p&gt;It is tempting to consider binding the access token to the device IP address at the time of initial login and this approach certainly would be more secure than simply echoing the current IP at the time of the PAR call. As a counter, though, the nature of mobile devices means that we should expect that a device’s IP address could change during the course of a session as the user moves from one network to another. We could end up restricting our application to a single IP address for the duration of the session if we were to follow this approach.&lt;/p&gt;
&lt;h2&gt;Extending the solution with PingOne MFA&lt;/h2&gt;
&lt;p&gt;Within our toolkit, we have the &lt;a href=&quot;https://docs.pingidentity.com/pingone/strong_authentication_mfa/p1_strong_authentication_start.html&quot;&gt;PingOne MFA&lt;/a&gt; service, Ping Identity’s strong authentication solution and one simple way for us to further improve our security posture here would be to use the &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/native-sdks/pingone-mfa-mobile-sdks/pingone-mfa-mobile-sdk-flows.html&quot;&gt;Automatic Device Authorisation&lt;/a&gt; capability offered by the &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/native-sdks/pingone-mfa-mobile-sdks.html&quot;&gt;PingOne MFA Mobile SDK&lt;/a&gt;. This approach allows us to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;identify a device through a cryptographic payload&lt;/li&gt;
&lt;li&gt;bind that device to a user record&lt;/li&gt;
&lt;li&gt;perform ad-hoc verification of that device based on a provided payload&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s revisit the end-to-end process description from two posts back — looking at how PingOne MFA is used to extend and further secure the flow.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;When Alice first registers her account with the Acme Inc native application, the app code embeds the PingOne MFA SDK, which generates a cryptographic device payload (bound to a private key in Alice’s key-store). This payload is passed to the PingOne backend as part of Alice’s registration request where the associated public key is paired with Alice’s user profile. The device is thus bound to Alice’s account.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alice opens the Acme Inc native application and clicks the “Login” button. The app uses the PingOne MFA SDK to generate a signed payload and includes this payload with the authentication request sent to the PingOne backend.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;She is authenticated via a native authentication experience (no browser pop-up) that uses DaVinci flow logic to collect Alice’s user credentials, validate these against the user directory and then calls the PingOne MFA backend to validate the payload from the device and ensure that the extracted device ID is bound to Alice’s account.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The flow completes successfully and includes the device ID as a claim in the access token that is sent to the app. The app responsibly and securely stores this access token (which is bound to her device via the &lt;em&gt;device_ID&lt;/em&gt; claim) on Alice’s behalf.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alice clicks the “Manage my profile” button in the app. The app code starts the PAR process using the client_id of the dedicated Session Transfer Client. It includes a scope such as “session_transfer” in the request, and generates a PKCE Code Challenge that it includes with the request. Note that the corresponding Code Verifier can be discarded at this point since this flow is not intended to ever result in an access token issuance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The app code makes a call to the PingOne MFA SDK to generate a fresh device payload. It then includes this payload, along with the access token that it already holds for the user, and the URL of the profile management application, in the PAR body that it constructs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It sends the PAR request to the authorisation server and obtains a response that contains a request_uri, which it appends to the AS’s authorization endpoint URL before opening a browser tab to initiate the front-channel flow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When the front-channel request hits the Authorisation Server, the request is validated (to ensure it has not expired or already been used) before being handed to the identity orchestration engine for further processing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The DaVinci orchestration engine must introspect or validate the provided access token to ensure that it is valid, has not expired and was indeed issued to the expected client (the mobile app). It should also extract the &lt;em&gt;device_ID&lt;/em&gt; claim from the token. It then calls the PingOne MFA backend to validate the payload from the device and ensure that the extracted device ID is bound to Alice’s account. It also ensures that the device ID extracted from the device payload matches the &lt;em&gt;device_ID&lt;/em&gt; claim extracted from the access token.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Should the above checks succeed, the orchestration engine extracts the “sub” claim from the access token to determine the appropriate user, and sets a session cookie in the browser for that same user.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At this point, the orchestration logic simply redirects the browser to the URL of the profile management application (the same URL that was provided via the PAR back-channel initiation in step 6). This is the step that interrupts the flow started by the Session Transfer Client and ensure that no auth code or access token is ever issued.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The profile management application now receives the browser redirect and is responsible for initiating its own OAuth/OpenID Connect Auth Code flow, just as it would had a browser opened the app URL directly. It should generate its own redirect to the AS, including its own client_id and redirect_uri, its own appropriate scopes and its own PKCE Code Challenge. The AS will start a user authentication process that should complete with no further user interaction required, based on the presence of the session cookie set in step 10.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The browser is thus redirected back to the profile management application with everything that is needed to display logged-in user content.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The above approach certainly closes a number of potential security loopholes; however the lift is admittedly rather heavy as a result. I would advise a comprehensive review of your own risk landscape (in consultation with your security team) in order to decide whether the additional overhead is warranted in your specific scenario.&lt;/p&gt;
&lt;h2&gt;In conclusion&lt;/h2&gt;
&lt;p&gt;This has been quite the journey — from a frustrated user staring at a “Contextual Cliff” to a high-assurance architecture that even a cynical CISO could love.&lt;/p&gt;
&lt;p&gt;To wrap up this series, let’s look at the path we’ve carved out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.pingidentity.com/blog/hardening-the-handoff-a-pattern-for-app-to-web-single-sign-on/&quot;&gt;&lt;strong&gt;Part 1: The Blueprint.&lt;/strong&gt;&lt;/a&gt; We defined the “Session Transfer Problem” and why relying on the “serendipitous” creation of browser cookies is no longer a viable strategy in a privacy-first, sandboxed world.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.pingidentity.com/blog/implementing-secure-session-transfer-a-field-guide-using-the-pingone-platform/&quot;&gt;&lt;strong&gt;Part 2: The Implementation.&lt;/strong&gt;&lt;/a&gt; We got our hands dirty with Kotlin and DaVinci, moving sensitive session data off the URL and into the secure back-channel via &lt;strong&gt;OAuth 2.0 Pushed Authorization Requests (PAR)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 3: The Hardening.&lt;/strong&gt; We moved from “making it work” to “making it resilient,” exploring the nuances of IP binding and the “gold standard” of cryptographic &lt;strong&gt;MFA Device Binding&lt;/strong&gt; to ensure the session stays where it belongs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reality of modern digital products is that hybrid architectures aren’t going anywhere. Our job as identity professionals isn’t to fight the existence of web-views, but to ensure that the transition between native and web remains invisible to the user and invincible to the attacker.&lt;/p&gt;
&lt;p&gt;Identity orchestration gives us the tools to build these “bridges” without compromising on either side of the UX-Security trade-off.&lt;/p&gt;
&lt;h2&gt;Over to You&lt;/h2&gt;
&lt;p&gt;I’d love to hear from the community:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How are you currently tackling session bridging in your own environments?&lt;/li&gt;
&lt;li&gt;Is the “heavy lift” of MFA-backed device binding a non-starter for your product teams, or has it become a non-negotiable requirement for your security auditors?&lt;/li&gt;
&lt;li&gt;Are there other “Contextual Cliffs” in your applications that you’re struggling to smooth over?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Drop a comment in the developer community with your thoughts, questions, or your own battle stories from the IAM trenches!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do you have thoughts or questions on this article? Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.CTi6Rdvk_Z15JE7d.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Implementing secure session transfer: a field guide using the PingOne platform</title><link>https://developer.pingidentity.com/blog/implementing-secure-session-transfer-a-field-guide-using-the-pingone-platform/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/implementing-secure-session-transfer-a-field-guide-using-the-pingone-platform/</guid><description>A hands-on field guide to wiring up a secure app-to-web session transfer on Android — walking through PingOne DaVinci flows, Pushed Authorization Requests, and PKCE to deliver seamless, token-validated SSO from a native mobile app to a web browser.</description><pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This post was originally published on &lt;a href=&quot;https://medium.com/@robertotto_31404/implementing-secure-session-transfer-a-field-guide-using-the-pingone-platform-a0410709e073&quot;&gt;Medium&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In my &lt;a href=&quot;https://developer.pingidentity.com/blog/hardening-the-handoff-a-pattern-for-app-to-web-single-sign-on/&quot;&gt;previous article&lt;/a&gt;, I established the conceptual blueprint for a hardened, standards-based mobile session transfer pattern. We identified that relying on shared browser cookies is a fragile strategy, often breaking under the weight of modern tracking prevention like Apple’s ITP or Google’s Privacy Sandbox. The alternative is a robust “bridge” architecture, moving beyond basic OAuth flows to integrate &lt;strong&gt;Pushed Authorization Requests (PAR, RFC 9126)&lt;/strong&gt; with Identity Orchestration for a secure session transition.&lt;/p&gt;
&lt;p&gt;In this field guide, I am moving beyond the “why” and deep into the “how, to provide a technical, domain-by-domain breakdown of the actual implementation.&lt;/p&gt;
&lt;h2&gt;Pre-requisites&lt;/h2&gt;
&lt;p&gt;This article is designed as a direct sequel, moving straight into the implementation mechanics. As such, I am assuming you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A fundamental understanding of the PAR and IP binding concepts established in &lt;a href=&quot;https://developer.pingidentity.com/blog/hardening-the-handoff-a-pattern-for-app-to-web-single-sign-on/&quot;&gt;part 1&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Access to a PingOne environment with SSO and DaVinci enabled.&lt;/li&gt;
&lt;li&gt;A basic proficiency with Kotlin/JetPack Compose (Android) although what is described here could be implemented in any other mobile platform with little difficulty&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Initial PingOne configuration&lt;/h2&gt;
&lt;p&gt;For this setup, we need the following to be configured in PingOne:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A Native Application client (this represents our mobile app and allows that app to obtain an access token using the SDK)&lt;/li&gt;
&lt;li&gt;A Session Transfer Application client (a stripped down client that exists only to trigger the PAR process)&lt;/li&gt;
&lt;li&gt;Some other “OIDC Web App” (I will be using the preconfigured “PingOne Self Service — MyAccount” application that is automatically bootstrapped with any environment)&lt;/li&gt;
&lt;li&gt;A user account for testing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Native Application client (1) should be configured as follows:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;PingOne Application configuration for Native Application Client&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;405&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/native-application-client-configuration.BvlDCCft_2e8l73.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/native-application-client-configuration.BvlDCCft_i2q4O.webp 640w, https://developer.pingidentity.com/blog/_astro/native-application-client-configuration.BvlDCCft_2e8l73.webp 720w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;Note here that &lt;em&gt;“API”&lt;/em&gt; is a custom &lt;a href=&quot;https://docs.pingidentity.com/pingone/applications/p1_resources.html&quot;&gt;Resource&lt;/a&gt; defined in PingOne with a corresponding &lt;em&gt;“application_api”&lt;/em&gt; scope. This represents access to native application backend API’s that will be granted via the application access token.&lt;/p&gt;
&lt;p&gt;We configure a “Simple U+PW” DaVinci flow to enable native user authentication using the &lt;a href=&quot;https://docs.pingidentity.com/sdks/latest/davinci/index.html&quot;&gt;Ping SDK&lt;/a&gt;. This flow will be described later.&lt;/p&gt;
&lt;p&gt;The Session Transfer Application client (2) should be configured as follows:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;PingOne Application configuration for Session Transfer Application Client&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;405&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/session-transfer-application-client-configuration.D0vSInXB_Z28VXgI.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/session-transfer-application-client-configuration.D0vSInXB_ZeajYC.webp 640w, https://developer.pingidentity.com/blog/_astro/session-transfer-application-client-configuration.D0vSInXB_Z28VXgI.webp 720w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;This client is also a public client (hence &lt;em&gt;Token Endpoint Authentication Method = None&lt;/em&gt;) but note that here we specify an invalid/unused &lt;em&gt;Redirect URI&lt;/em&gt; since this client will only be used for PAR initiation, not token issuance. We require a Pushed Authorization Request with a short timeout and also define a custom scope of &lt;em&gt;web_session_only&lt;/em&gt;. The combination of the invalid Redirect URI, the custom scope and the PKCE enforcement should provide sufficient (and redundant) protection against this client being misused for token issuance.&lt;/p&gt;
&lt;p&gt;The “Session Transfer” DaVinci Flow will be described later.&lt;/p&gt;
&lt;h2&gt;The first DaVinci flow&lt;/h2&gt;
&lt;p&gt;In order to deliver a fully-native login experience within our application, we will implement the &lt;a href=&quot;https://docs.pingidentity.com/sdks/latest/davinci/index.html&quot;&gt;PingSDK with the DaVinci Client module&lt;/a&gt;. This approach requires us to define a PingOne DaVinci authentication flow for initial authentication of the user within our application (the step that allows us to obtain an access token).&lt;/p&gt;
&lt;p&gt;For this example we will use a trivially simple DaVinci flow, shown below. We use the bootstrapped “Example — Sign On” form from the &lt;a href=&quot;https://docs.pingidentity.com/pingone/user_experience/p1_forms.html&quot;&gt;PingOne Forms&lt;/a&gt; service.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Simple Username and Password DaVinci Authentication Flow&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;291&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/simple-username-and-password-davinci-authentication-flow.Ct5AZ19X_rkVBQ.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/simple-username-and-password-davinci-authentication-flow.Ct5AZ19X_1sS5Lf.webp 640w, https://developer.pingidentity.com/blog/_astro/simple-username-and-password-davinci-authentication-flow.Ct5AZ19X_rkVBQ.webp 720w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;A more detailed explanation of how to use DaVinci to enable authentication is well beyond the scope of this discussion, but should the above prove insufficient, help is always available via the comments section!&lt;/p&gt;
&lt;h2&gt;Using the Ping SDK to get a user access token&lt;/h2&gt;
&lt;p&gt;Paul Simon told us that there are 50 ways to leave your lover — what I will add to this insight is that there are at least twice as many ways to obtain an access token within a native application! I’m going to provide some very simple sample code below showing just one of these, using Android with Kotlin and JetPack Compose. Here, we use the &lt;a href=&quot;https://docs.pingidentity.com/sdks/latest/davinci/index.html&quot;&gt;PingSDK&lt;/a&gt; to trigger the above DaVinci flow to perform simple username and password authentication.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MainActivity&lt;/span&gt;&lt;span&gt; : ComponentActivity() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    sealed&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; DaVinciState&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        object Idle : &lt;/span&gt;&lt;span&gt;DaVinciState&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        data class &lt;/span&gt;&lt;span&gt;Waiting&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; text&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;DaVinciState&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        data class &lt;/span&gt;&lt;span&gt;ShowForm&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; collectors&lt;/span&gt;&lt;span&gt;: List&amp;lt;Collector&amp;lt;*&amp;gt;&amp;gt;) : &lt;/span&gt;&lt;span&gt;DaVinciState&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        data class &lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;DaVinciState&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        data class &lt;/span&gt;&lt;span&gt;Success&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; user&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;?) : &lt;/span&gt;&lt;span&gt;DaVinciState&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    var currentState by mutableStateOf&amp;lt;&lt;/span&gt;&lt;span&gt;DaVinciState&lt;/span&gt;&lt;span&gt;&amp;gt;(DaVinciState.Idle)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    var userID: String? = null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    var username: String? = null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    var accessToken: String? = null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    val CLIENT_ID = &quot;&amp;lt;&lt;/span&gt;&lt;span&gt;PingOne&lt;/span&gt;&lt;span&gt; Client&lt;/span&gt;&lt;span&gt; ID&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; Native&lt;/span&gt;&lt;span&gt; Application&lt;/span&gt;&lt;span&gt; Client&lt;/span&gt;&lt;span&gt;&amp;gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    val P1_BASE_URL = &quot;https:&lt;/span&gt;&lt;span&gt;//auth.pingone.eu/&amp;lt;PingOne Environment ID&amp;gt;/&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    val daVinci = DaVinci {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        module&lt;/span&gt;&lt;span&gt;(Oidc) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            clientId &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CLIENT_ID&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            discoveryEndpoint &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; P1_BASE_URL &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot;as/.well-known/openid-configuration&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            scopes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mutableSetOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;openid&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;application_api&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            redirectUri &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;com.example.myapp://oauth2redirect&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    var&lt;/span&gt;&lt;span&gt; node&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Node&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    override fun &lt;/span&gt;&lt;span&gt;onCreate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;savedInstanceState&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        enableEdgeToEdge&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setContent {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            SessionTransferTheme {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                MainLayout&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    suspend fun &lt;/span&gt;&lt;span&gt;startNativeLogin&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        node &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; daVinci.&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        handleNode&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    suspend fun &lt;/span&gt;&lt;span&gt;submitCreds&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val submitCollector &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ((node as ContinueNode).collectors[&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;]) as SubmitCollector&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        submitCollector.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; submitCollector.key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        currentState &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; DaVinciState.&lt;/span&gt;&lt;span&gt;Waiting&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        node &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (node as ContinueNode).&lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        handleNode&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    suspend fun &lt;/span&gt;&lt;span&gt;handleNode&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        when&lt;/span&gt;&lt;span&gt; (node) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            is ContinueNode &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                currentState &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; DaVinciState.&lt;/span&gt;&lt;span&gt;ShowForm&lt;/span&gt;&lt;span&gt;((node as ContinueNode).collectors)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            is ErrorNode &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                currentState &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; DaVinciState.&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Login Error&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            is FailureNode &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                currentState &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; DaVinciState.&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Login Failure&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            is SuccessNode &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                val user &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; daVinci.&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                val userInfo&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Result&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;JsonObject, OidcError&lt;/span&gt;&lt;span&gt;&amp;gt;?&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    user&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;userinfo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                if&lt;/span&gt;&lt;span&gt; (userInfo is Result.Success) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    userID &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; userInfo.value[&lt;/span&gt;&lt;span&gt;&quot;sub&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.jsonPrimitive&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.content&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    username &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; userInfo.value[&lt;/span&gt;&lt;span&gt;&quot;username&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.jsonPrimitive&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.content&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                val tokenResponse &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; user&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;token&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                if&lt;/span&gt;&lt;span&gt; (tokenResponse is Result.Success) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    val token &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; tokenResponse.value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    accessToken &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; token.accessToken&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    currentState &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; DaVinciState.&lt;/span&gt;&lt;span&gt;Success&lt;/span&gt;&lt;span&gt;(user)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            else&lt;/span&gt;&lt;span&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    @&lt;/span&gt;&lt;span&gt;Composable&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fun &lt;/span&gt;&lt;span&gt;MainLayout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;modifier&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Modifier&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;Modifier&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val scope &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; rememberCoroutineScope&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        Column&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; modifier&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                .&lt;/span&gt;&lt;span&gt;fillMaxSize&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                .&lt;/span&gt;&lt;span&gt;padding&lt;/span&gt;&lt;span&gt;(24.dp),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            horizontalAlignment &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Alignment.CenterHorizontally,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            verticalArrangement &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Arrangement.Center&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            when&lt;/span&gt;&lt;span&gt; (val state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; currentState) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                is DaVinciState.Idle &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    // LOGGED OUT STATE&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Secure Session Transfer&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        style &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.typography.headlineMedium&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Spacer&lt;/span&gt;&lt;span&gt;(modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Modifier.&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;(8.dp))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Authenticate to begin the app-to-web flow.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        style &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.typography.bodyMedium,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        color &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.colorScheme.onSurfaceVariant&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Spacer&lt;/span&gt;&lt;span&gt;(modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Modifier.&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;(32.dp))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Button&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        onClick &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            scope.launch {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                                startNativeLogin&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Modifier&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            .&lt;/span&gt;&lt;span&gt;fillMaxWidth&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            .&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;(56.dp)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    ) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Login with PingOne&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                is DaVinciState.Waiting &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    CircularProgressIndicator&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                is DaVinciState.Error &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Error&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        style &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.typography.headlineMedium&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Spacer&lt;/span&gt;&lt;span&gt;(modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Modifier.&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;(8.dp))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;$state.message&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        style &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.typography.bodyMedium,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        color &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.colorScheme.onSurfaceVariant&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Spacer&lt;/span&gt;&lt;span&gt;(modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Modifier.&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;(8.dp))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Button&lt;/span&gt;&lt;span&gt;(onClick &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { currentState &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; DaVinciState.Idle}) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Restart&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                is DaVinciState.ShowForm &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Secure Session Transfer&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        style &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.typography.headlineMedium&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Spacer&lt;/span&gt;&lt;span&gt;(modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Modifier.&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;(8.dp))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Enter your login details.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        style &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.typography.bodyMedium,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        color &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.colorScheme.onSurfaceVariant&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    val collectors &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; state.collectors&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    // USERNAME&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    val textCollector &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; collectors[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;] as TextCollector&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    val usernameText &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        remember&lt;/span&gt;&lt;span&gt;() { &lt;/span&gt;&lt;span&gt;mutableStateOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TextFieldValue&lt;/span&gt;&lt;span&gt;(textCollector.value)) }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    TextField&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; usernameText.value,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; modifier&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            .&lt;/span&gt;&lt;span&gt;wrapContentWidth&lt;/span&gt;&lt;span&gt;(Alignment.CenterHorizontally)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            .&lt;/span&gt;&lt;span&gt;fillMaxWidth&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            .&lt;/span&gt;&lt;span&gt;padding&lt;/span&gt;&lt;span&gt;(top &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 14.dp),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        shape &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; RoundedCornerShape&lt;/span&gt;&lt;span&gt;(8.dp),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        onValueChange &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            usernameText.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; it&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            textCollector.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; it.text&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        label &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                                text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; textCollector.label,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    // PASSWORD&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    val passwordCollector &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; collectors[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;] as PasswordCollector&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    val passwordText &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        remember { &lt;/span&gt;&lt;span&gt;mutableStateOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TextFieldValue&lt;/span&gt;&lt;span&gt;(passwordCollector.value)) }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    TextField&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; passwordText.value,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; modifier&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            .&lt;/span&gt;&lt;span&gt;wrapContentWidth&lt;/span&gt;&lt;span&gt;(Alignment.CenterHorizontally)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            .&lt;/span&gt;&lt;span&gt;fillMaxWidth&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            .&lt;/span&gt;&lt;span&gt;padding&lt;/span&gt;&lt;span&gt;(top &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 4.dp),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        onValueChange &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            passwordText.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; it&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            passwordCollector.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; it.text&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        label &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                                text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; passwordCollector.label&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        visualTransformation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; PasswordVisualTransformation&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        keyboardOptions &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            KeyboardOptions&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                                keyboardType &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; KeyboardType.Password,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    // BUTTON&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Button&lt;/span&gt;&lt;span&gt;(onClick &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { scope.launch { &lt;/span&gt;&lt;span&gt;submitCreds&lt;/span&gt;&lt;span&gt;() } }) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Submit&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                is DaVinciState.Success &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Logged in&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        style &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.typography.headlineMedium&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Hi $username&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        style &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.typography.bodyMedium,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        color &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.colorScheme.onSurfaceVariant&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Spacer&lt;/span&gt;&lt;span&gt;(modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Modifier.&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;(32.dp))           &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                else&lt;/span&gt;&lt;span&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above should be “more or less” directly usable to build an example application, as long as you first &lt;a href=&quot;https://docs.pingidentity.com/sdks/latest/davinci/configuration.html&quot;&gt;follow the prerequisites required by the Ping SDK&lt;/a&gt;. It is, of course, provided with no warranties of any kind and must be used for your own education only. Right now, our app is pretty simple, as shown below.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;User Experience for our simple Android App&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;405&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/user-experience-for-our-simple-android-app.D_pfO37q_sCgx8.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/user-experience-for-our-simple-android-app.D_pfO37q_ZJ4EiB.webp 640w, https://developer.pingidentity.com/blog/_astro/user-experience-for-our-simple-android-app.D_pfO37q_sCgx8.webp 720w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;Extending the app to launch web content&lt;/h2&gt;
&lt;p&gt;Before we dive into the magic of the DaVinci flow that makes our final goal achievable, let’s extend the app code from above to launch the PingOne MyAccount web console in a browser view.&lt;/p&gt;
&lt;p&gt;First, we add a button to the UI by modifying the MainLayout compose function as below&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;Composable&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fun &lt;/span&gt;&lt;span&gt;MainLayout&lt;/span&gt;&lt;span&gt;(modifier&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Modifier) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* SNIP */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            when&lt;/span&gt;&lt;span&gt; (val state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; currentState) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* SNIP */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                is DaVinciState.Success &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Logged in&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        style &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.typography.headlineMedium&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Hi $username&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        style &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.typography.bodyMedium,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        color &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MaterialTheme.colorScheme.onSurfaceVariant&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Spacer&lt;/span&gt;&lt;span&gt;(modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Modifier.&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;(32.dp))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//Start of new code &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Button&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        onClick &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            scope.launch {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                                launchUserProfileInBrowser&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        modifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Modifier&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            .&lt;/span&gt;&lt;span&gt;fillMaxWidth&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            .&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;(56.dp)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    ) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;View User Profile&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    } &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// End of new code &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                else&lt;/span&gt;&lt;span&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we add a function to perform the PAR initiation and launch the browser with the received &lt;em&gt;request_uri&lt;/em&gt; value.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;suspend fun &lt;/span&gt;&lt;span&gt;launchUserProfileInBrowser&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val client &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; HttpClient&lt;/span&gt;&lt;span&gt;(Android) {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val verifier &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; PkceGenerator.&lt;/span&gt;&lt;span&gt;generateVerifier&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val challenge &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; PkceGenerator.&lt;/span&gt;&lt;span&gt;generateChallenge&lt;/span&gt;&lt;span&gt;(verifier)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val myIP &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; getPublicIp&lt;/span&gt;&lt;span&gt;(client)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val requestTimestamp &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; System.&lt;/span&gt;&lt;span&gt;currentTimeMillis&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val expiresAt &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requestTimestamp &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 10000&lt;/span&gt;&lt;span&gt; // 10 seconds leeway&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val response&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; HttpResponse &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; client.&lt;/span&gt;&lt;span&gt;submitForm&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            url &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;https://auth.pingone.eu/&amp;lt;PingOne Environment ID&amp;gt;/as/par&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            formParameters &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; parameters {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;client_id&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;PingOne Client ID for Session Transfer Application Client&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;response_type&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;code&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;scope&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;openid web_session_only&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;redirect_uri&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;https://unused&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;request_token&quot;&lt;/span&gt;&lt;span&gt;, accessToken &lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;code_challenge&quot;&lt;/span&gt;&lt;span&gt;, challenge)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;code_challenge_method&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;S256&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;ip_address&quot;&lt;/span&gt;&lt;span&gt;, myIP&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;exp&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;$expiresAt&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;application_URL&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;https://apps.pingone.eu/&amp;lt;PingOne Environment ID&amp;gt;/myaccount/&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (response.status.&lt;/span&gt;&lt;span&gt;isSuccess&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            val responseBody &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;bodyAsText&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            val jsonResponse &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; JSONObject&lt;/span&gt;&lt;span&gt;(responseBody)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            val requestUri &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jsonResponse.&lt;/span&gt;&lt;span&gt;getString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;request_uri&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            val authorizeUrl &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Uri.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;https://auth.pingone.eu/&amp;lt;PingOne Environment ID&amp;gt;/as/authorize&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                .&lt;/span&gt;&lt;span&gt;buildUpon&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                .&lt;/span&gt;&lt;span&gt;appendQueryParameter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;client_id&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;PingOne Client ID for Session Transfer Application Client&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                .&lt;/span&gt;&lt;span&gt;appendQueryParameter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;request_uri&quot;&lt;/span&gt;&lt;span&gt;, requestUri)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                .&lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            val customTabsIntent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CustomTabsIntent.&lt;/span&gt;&lt;span&gt;Builder&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            customTabsIntent.&lt;/span&gt;&lt;span&gt;launchUrl&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, authorizeUrl)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // Handle HTTP errors (e.g. 400 Bad Request)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;PAR Error: ${response.status}&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, we need a bit of utility code, as below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// A utility method to figure out our own IP&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;suspend fun &lt;/span&gt;&lt;span&gt;getPublicIp&lt;/span&gt;&lt;span&gt;(client&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; HttpClient)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // Use a lightweight reflector&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            client.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;https://api.ipify.org&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;bodyAsText&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (e: &lt;/span&gt;&lt;span&gt;Exception&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//A utility object to help with PKCE&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;object PkceGenerator {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fun &lt;/span&gt;&lt;span&gt;generateVerifier&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; String {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val secureRandom &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; SecureRandom&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val code &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ByteArray&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        secureRandom.&lt;/span&gt;&lt;span&gt;nextBytes&lt;/span&gt;&lt;span&gt;(code)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; Base64.&lt;/span&gt;&lt;span&gt;encodeToString&lt;/span&gt;&lt;span&gt;(code, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fun &lt;/span&gt;&lt;span&gt;generateChallenge&lt;/span&gt;&lt;span&gt;(verifier&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; String)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; String {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val bytes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; verifier.&lt;/span&gt;&lt;span&gt;toByteArray&lt;/span&gt;&lt;span&gt;(Charsets.US_ASCII)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val messageDigest &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MessageDigest.&lt;/span&gt;&lt;span&gt;getInstance&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;SHA-256&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        val digest &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; messageDigest.&lt;/span&gt;&lt;span&gt;digest&lt;/span&gt;&lt;span&gt;(bytes)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; Base64.&lt;/span&gt;&lt;span&gt;encodeToString&lt;/span&gt;&lt;span&gt;(digest, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What we have done above is to create a Pushed Authorization Request, where the following have been added as custom parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;request_token:&lt;/strong&gt; the user’s access token&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ip-address:&lt;/strong&gt; the current (external) IP address of the device&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;exp:&lt;/strong&gt; a timestamp for 10 seconds from now&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;application_URL:&lt;/strong&gt; the URL of the actual application the user needs to be redirected to once the session has been created.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next is to craft a DaVinci flow that can use these inputs to deliver the desired outcome.&lt;/p&gt;
&lt;h2&gt;The second (and far more interesting) DaVinci flow&lt;/h2&gt;
&lt;p&gt;Before we dive into flow logic, we need to understand a bit about how PingOne SSO and DaVinci work together to handle pushed authorisation requests. In this case, because our Session Transfer Application client has been configured to require PAR and because it has the required flow linked via a DaVinci Policy, any PAR request (once validated to protocol correctness) &lt;a href=&quot;https://docs.pingidentity.com/davinci/integrating_flows_into_applications/davinci_launch_flow_redirect.html#referencing-pingone-data-in-the-flow&quot;&gt;will be passed to the DaVinci flow as a standard PingOne Flow invocation&lt;/a&gt;, with any custom parameters provided via the &lt;em&gt;{{global.parameters.authorizationRequest}}&lt;/em&gt; object. Hence to obtain the access token in the flow, we would use &lt;em&gt;{{global.parameters.authorizationRequest.request_token}}&lt;/em&gt; and so on.&lt;/p&gt;
&lt;p&gt;Our example flow then looks as follows:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example DaVinci flow for session transfer&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;168&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/example-davinci-flow-for-session-transfer.KSIUEl39_bk5h1.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/example-davinci-flow-for-session-transfer.KSIUEl39_Z26ahuR.webp 640w, https://developer.pingidentity.com/blog/_astro/example-davinci-flow-for-session-transfer.KSIUEl39_bk5h1.webp 720w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;The steps are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Check expiry of the request.&lt;/strong&gt; This is done by using a Function node to compare the exp claim in the authorisation request to the current timestamp. Note that this check is perhaps not entirely necessary, since the PAR functionality in Ping One already constrains the validity of the request_uri, but it is included here as just one of any number of additional security checks that could be added to the flow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Function node to check expiry&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;656&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/function-node-to-check-expiry.BTCOyUOG_Z2oCRux.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/function-node-to-check-expiry.BTCOyUOG_9YyuB.webp 640w, https://developer.pingidentity.com/blog/_astro/function-node-to-check-expiry.BTCOyUOG_Z2oCRux.webp 720w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Check IP address:&lt;/strong&gt; Again we use a function node to check the current IP address as seen by DaVinci against the address that was included in the PAR payload.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Function to check IP address&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;609&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/function-to-check-ip-address.BJ7FrHOy_Zubrmt.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/function-to-check-ip-address.BJ7FrHOy_Z2vQNUX.webp 640w, https://developer.pingidentity.com/blog/_astro/function-to-check-ip-address.BJ7FrHOy_Zubrmt.webp 720w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Validate request token:&lt;/strong&gt; this is the critical step where we ensure that the access token passed in the PAR payload is valid before extracting the token subject to create a session. In this example, I am using DaVinci’s Token Management -&amp;gt; Validate JWT Token capability and using the PngOne’s environment’s JWKS endpoint to provide the public key for validation. An alternative approach would be to make a REST call to the token introspection endpoint.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Using the Token Management connector to validate the request token&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;545&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/using-the-token-management-connector-to-validate-the-request-token.BCswsWti_1hJjpO.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/using-the-token-management-connector-to-validate-the-request-token.BCswsWti_LmU8n.webp 640w, https://developer.pingidentity.com/blog/_astro/using-the-token-management-connector-to-validate-the-request-token.BCswsWti_1hJjpO.webp 720w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Create a (web) session for the user:&lt;/strong&gt; having ensured that a valid token has been provided, we are free to create the necessary session, represented by the PingOne ST cookie in the user’s browser. The simplest way to do this is to use the PingOne Authentication -&amp;gt; Create or Update Session capability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Using the PingOne Authentication connector to create a session&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;793&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/using-the-pingone-authentication-connector-to-create-a-session.Bux0l59N_ZNxoDI.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/using-the-pingone-authentication-connector-to-create-a-session.Bux0l59N_TXtCs.webp 640w, https://developer.pingidentity.com/blog/_astro/using-the-pingone-authentication-connector-to-create-a-session.Bux0l59N_ZNxoDI.webp 720w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Redirect to the target application:&lt;/strong&gt; The final step is to generate a redirect so that the actual application (in our example the MyAccount app) can start its own OIDC flow to transparently log the user in based on the session cookie we just created. I’m using a simple Custom HTML template node in DaVinci with a Javascript redirect.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;A custom template to trigger a redirect to the target application URL&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;500&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/a-custom-template-to-trigger-a-redirect-to-the-target-application-url.C8GNz10O_ZuRPuN.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/a-custom-template-to-trigger-a-redirect-to-the-target-application-url.C8GNz10O_1lH5LG.webp 640w, https://developer.pingidentity.com/blog/_astro/a-custom-template-to-trigger-a-redirect-to-the-target-application-url.C8GNz10O_ZuRPuN.webp 720w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;A final test&lt;/h2&gt;
&lt;p&gt;We should have everything we need and our application experience now looks as follows:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;End-to-end experience in sample app&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;405&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/end-to-end-experience-in-sample-app.COmI2M_R_Z2rz8Rm.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/end-to-end-experience-in-sample-app.COmI2M_R_Zdjs1J.webp 640w, https://developer.pingidentity.com/blog/_astro/end-to-end-experience-in-sample-app.COmI2M_R_Z2rz8Rm.webp 720w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;In our final episode…&lt;/h2&gt;
&lt;p&gt;In the next article, I will build on this approach and look at a number of advanced techniques we could use to make this hand-off process even more secure.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do you have thoughts or questions on this article? Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.CiXag4ms_27rSQ8.webp" medium="image" type="image/webp" width="2386" height="1256"/></item><item><title>Ping Orchestration SDKs 2.0</title><link>https://developer.pingidentity.com/blog/ping-orchestration-sdks-2-0/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/ping-orchestration-sdks-2-0/</guid><description>One SDK family for DaVinci, AIC/PingAM journeys, and OIDC across web and native. Here&apos;s what changed, what it means for your apps, and how to get started.</description><pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ping Orchestration SDKs 2.0 are our next generation of client-side SDKs for JavaScript, iOS, and Android! They give you one unified way to connect your apps to PingOne DaVinci flows, PingOne Advanced Identity Cloud / PingAM journeys, and OIDC redirect with any OIDC-compliant server. You integrate once, then evolve journeys and flows on the server without constantly rewriting your app.&lt;/p&gt;
&lt;p&gt;Want to get started now? Jump to &lt;a href=&quot;#getting-started&quot;&gt;Getting Started&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Key features&lt;/h2&gt;
&lt;p&gt;The SDKs handle multi-client orchestration for you. A single app can work with multiple OAuth clients simultaneously, such as one for primary login and another for step-up or high-risk actions. The SDK manages tokens and clients so your code can focus on business logic rather than OAuth details.&lt;/p&gt;
&lt;p&gt;They also integrate Ping’s universal services through the orchestration layer. The SDK can collect device and behavioral context (for example, with PingOne Protect) and feed that into your DaVinci flows or AIC/PingAM journeys so you can drive risk-based decisions from a single place. As we add more services, like PingOne MFA for PingOne or privacy-preserving biometrics with PingOne Keyless, they appear through the same orchestration model instead of new one-off integrations.&lt;/p&gt;
&lt;p&gt;Across JavaScript, Android, and iOS, the orchestration model is consistent. Teams can reuse patterns across web and native apps, and new developers can ramp up faster because the concepts look and feel the same on each platform.&lt;/p&gt;
&lt;h2&gt;Why Orchestration 2.0?&lt;/h2&gt;
&lt;p&gt;Historically, DaVinci flows and AIC/PingAM journeys had separate SDK stories. Orchestration 2.0 brings them together into one orchestration-first family across JS, Android, and iOS.&lt;/p&gt;
&lt;p&gt;The older ForgeRock SDKs evolved into several large libraries. Orchestration 2.0 replaces that with a modular, pluggable design with networking, journeys, FIDO/passkeys, MFA, device binding, device ID/profiling, and more, so you only include what you need and stay aligned with modern Kotlin, Swift, and JS practices. This single orchestration plane is where we focus on performance, security, and new capabilities.&lt;/p&gt;
&lt;h2&gt;Benefits vs. ForgeRock SDKs&lt;/h2&gt;
&lt;p&gt;If you use the ForgeRock JS, Android, or iOS SDKs today, Orchestration 2.0 gives you one SDK family for both DaVinci and journeys. The same core ideas apply whether you’re building SPAs or native apps with AIC/PingAM.&lt;/p&gt;
&lt;p&gt;The architecture is more modular: journey orchestration, FIDO2/passkeys, OATH and Push MFA, device binding, migration helpers, device ID/profiling, jailbreak/root detection, and reCAPTCHA Enterprise are all focused modules. You import only what you need, which helps with footprint and security. The orchestration model also adds built-in multi-client support, so scenarios like primary login plus step-up are handled by the SDK instead of custom token juggling. Everything sits on modern JavaScript, Android, and iOS foundations.&lt;/p&gt;
&lt;h2&gt;Why new SDKs vs. evolving ForgeRock SDKs&lt;/h2&gt;
&lt;p&gt;The ForgeRock SDKs assumed separate paths for DaVinci and AIC/PingAM and concentrated a lot of behavior into a small number of libraries. That made it hard to keep adding features, support richer token patterns, and plug in new services without extra complexity.&lt;/p&gt;
&lt;p&gt;With Orchestration 2.0, we refactored orchestration into a new architecture, split cross-cutting concerns (networking, storage, device binding, FIDO, MFA, and so on) into modules, and made these SDKs the contract for all new integrations and universal services. The goal is that app development feels simpler and more consistent, even as the platform becomes more powerful behind the scenes.&lt;/p&gt;
&lt;h2&gt;If you use ForgeRock SDKs with PingOne AIC/PingAM&lt;/h2&gt;
&lt;p&gt;The ForgeRock JS, Android, and iOS SDKs are now deprecated. They’ll receive critical security and bug fixes for about two years after Orchestration 2.0 GA, until 15 April 2028. After that, they won’t be updated, and the expectation is that customers move to the Orchestration SDKs.&lt;/p&gt;
&lt;p&gt;For most AIC/PingAM customers, treat migration as a structured project.
Start with the &lt;a href=&quot;https://developer.pingidentity.com/orchsdks/journey/migration.html&quot;&gt;detailed guides&lt;/a&gt;
to understand
how to migrate from the ForgeRock SDKs to the Ping Orchestration SDKs.
These guides provide concrete examples
of how to migrate existing ForgeRock integrations to the new SDKs
and make it easier to map journey callbacks,
device behavior, MFA flows such as OATH, and Push.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Use AI Coding Agents
(GitHub Copilot, Cursor, Claude Code, etc) to expedite migration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Context-ready&lt;/strong&gt;: We’ve included structured &lt;code&gt;migration.md&lt;/code&gt; files for the &lt;a href=&quot;https://github.com/ForgeRock/ping-android-sdk/blob/master/journey/migration.md&quot;&gt;&lt;code&gt;ping-android-sdk&lt;/code&gt;&lt;/a&gt; and the
&lt;a href=&quot;https://github.com/ForgeRock/ping-ios-sdk/blob/master/Journey/migration.md&quot;&gt;&lt;code&gt;ping-ios-sdk&lt;/code&gt;&lt;/a&gt;. You can feed these files directly into your AI’s context to help it automatically map old ForgeRock classes to the new Orchestration 2.0 modules.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated Refactoring&lt;/strong&gt;: Use prompts to help the agent rewrite your networking and journey logic, significantly reducing the manual effort of updating callback handlers and device binding code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coming Soon:&lt;/strong&gt; We are currently developing a dedicated We now have Agent Skills will act as a migration specialist, providing real-time, project-specific guidance for moving your unique identity flows into the new orchestration plane. Refer to our &lt;a href=&quot;https://github.com/pingidentity/ping-sdk-agent-skills&quot;&gt;ping-sdk-agent-skills&lt;/a&gt; repo or our &lt;a href=&quot;https://developer.pingidentity.com/build-with-ai/&quot;&gt;build with AI&lt;/a&gt; documentation for more information.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;A common pattern is to run the new and legacy SDKs side by side. The Orchestration SDKs can coexist with the old ones, and dedicated migration modules for device binding, device ID, and OATH/Push (plus encrypted state helpers) are there to migrate identifiers without forcing most users to re-enroll. After you’re confident in the new model, you initialize the Orchestration SDKs in your apps, move UI and callback handling to the new patterns, and keep most behavior in AIC/PingAM journeys so you can keep iterating without constant app releases.&lt;/p&gt;
&lt;p&gt;A typical migration has three phases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with inventory and design: list all apps and journeys using the ForgeRock SDKs, classify the flows they rely on: sign on, registration, self-service, MFA, device binding, passkeys, and so on, and call out any reliance on Transactional Authorization or the Token Vault.&lt;/li&gt;
&lt;li&gt;From there, you run a pilot on one representative mobile or web app. You integrate the Orchestration SDKs in a feature branch, use the migration guides and sample apps (with or without AI assistance) to map journeys to the new orchestration model, and validate device binding and device ID migration in a non-production environment.&lt;/li&gt;
&lt;li&gt;After you feel confident, you move into rollout: promote the pilot to production, extend the new SDKs to other apps and platforms, and then remove the legacy SDK dependencies when you have full coverage.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the Orchestration 2.0 release, Transactional Authorization and the JavaScript Token Vault are not included. If you rely on either in production, contact your Ping representative and open a support case so we can work through options with you rather than attempting a direct, like-for-like migration.&lt;/p&gt;
&lt;h2&gt;Docs, Login Widget, and resources&lt;/h2&gt;
&lt;p&gt;DaVinci SDK docs have been folded into the Orchestration SDK documentation. DaVinci-focused guides and tutorials now live there, and older DaVinci-only pages will redirect or point you to the new docs. If you used the “DaVinci module” docs before, the Orchestration SDK JavaScript, Android, and iOS docs are now your primary reference.&lt;/p&gt;
&lt;p&gt;The Login Widget now lives in its own &lt;a href=&quot;https://developer.pingidentity.com/login-widget/index.html&quot;&gt;repository&lt;/a&gt;,
allowing it
to evolve independently while remaining part of the orchestration story.
It is referenced from both the deprecated ForgeRock SDK docs and the Orchestration SDK docs as one of the available integration options alongside a fully custom UI.&lt;/p&gt;
&lt;p&gt;Sample applications for JavaScript, Android, and iOS SDK and the SDK repositories and packages are all &lt;a href=&quot;https://developer.pingidentity.com/sdks.html&quot;&gt;linked&lt;/a&gt; from the Ping Developer Portal. The portal acts as the central entry point for Orchestration SDK documentation, API references, tutorials, so you can find everything in one place.&lt;/p&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;To get started with the Orchestration SDKs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reference our tutorials for &lt;a href=&quot;https://developer.pingidentity.com/orchsdks/davinci/tutorials.html&quot;&gt;DaVinci&lt;/a&gt; or &lt;a href=&quot;https://developer.pingidentity.com/orchsdks/journey/try-it-out.html&quot;&gt;AIC/PingAM&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Clone our SDK &lt;a href=&quot;https://github.com/ForgeRock/sdk-sample-apps&quot;&gt;sample apps&lt;/a&gt; repository and enter relevant server details to understand the fundamentals and walk through core orchestration flows end to end, so you can compare a working reference to your current setup before touching production or wondering how to integrate authentication flows into your existing mobile and web apps.&lt;/li&gt;
&lt;li&gt;Integrate into your existing app.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The SDKs are available from standard locations: JavaScript on GitHub and NPM, Android on GitHub and Maven, and iOS on GitHub and Swift Package Manager. The Ping Developer Portal will be the main entry point, bringing together orchestration docs, sample apps, and API references in one place.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ℹ️ &lt;strong&gt;Note:&lt;/strong&gt; Soon you’ll see We now have AI-powered Agent Skills and tools to help integrate into existing apps or set up sample apps quickly at our &lt;a href=&quot;https://github.com/pingidentity/ping-sdk-agent-skills&quot;&gt;ping-sdk-agent-skills&lt;/a&gt; repo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SDK Sample applications: &lt;a href=&quot;https://github.com/ForgeRock/sdk-sample-apps&quot;&gt;https://github.com/ForgeRock/sdk-sample-apps&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SDK repositories:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;iOS: &lt;a href=&quot;https://github.com/ForgeRock/ping-ios-sdk/tree/master&quot;&gt;https://github.com/ForgeRock/ping-ios-sdk/tree/master&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Android: &lt;a href=&quot;https://github.com/ForgeRock/ping-android-sdk/tree/master&quot;&gt;https://github.com/ForgeRock/ping-android-sdk/tree/master&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JS: &lt;a href=&quot;https://github.com/ForgeRock/ping-javascript-sdk/tree/main&quot;&gt;https://github.com/ForgeRock/ping-javascript-sdk/tree/main&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All Ping Identity’s SDKs: &lt;a href=&quot;https://developer.pingidentity.com/sdks.html&quot;&gt;https://developer.pingidentity.com/sdks.html&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Orchestration SDK Documentation: &lt;a href=&quot;https://developer.pingidentity.com/orchsdks/index.html&quot;&gt;https://developer.pingidentity.com/orchsdks/index.html&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PingOne Advanced Identity Cloud/PingAM Login Widget: &lt;a href=&quot;https://developer.pingidentity.com/login-widget/index.html&quot;&gt;https://developer.pingidentity.com/login-widget/index.html&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deprecated ForgeRock SDK Documentation: &lt;a href=&quot;https://docs.pingidentity.com/sdks/latest/index.html&quot;&gt;https://docs.pingidentity.com/sdks/latest/index.html&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do you have thoughts or questions on this article? Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.BKV5lbkG_1jPEcR.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Hardening the handoff: a pattern for app-to-web single sign-on</title><link>https://developer.pingidentity.com/blog/hardening-the-handoff-a-pattern-for-app-to-web-single-sign-on/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/hardening-the-handoff-a-pattern-for-app-to-web-single-sign-on/</guid><description>A walkthrough of a standards-based pattern for securely transferring an OAuth session from a native mobile app to a web browser using Pushed Authorization Requests (PAR).</description><pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This post was originally published on &lt;a href=&quot;https://medium.com/@robertotto_31404/hardening-the-handoff-a-pattern-for-app-to-web-single-sign-on-6caf0c82786b&quot;&gt;Medium&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We’ve all been there, haven’t we? That shiny mobile app that you’ve just downloaded from your favourite brand, the one that’s offered you a really clean and modern sign in experience (without browser hand-offs, perhaps even using a passkey for quick login), suddenly and inexplicably spits you out into a browser view when you tap a particular button or menu item. Hybrid apps — those that combine both native and web elements — are an unavoidable reality for many organisations (and their users) as they look to balance time to market on new features with ever-stretched engineering budgets and the technical debt further down the stack that they’ve never quite managed to pay off.&lt;/p&gt;
&lt;p&gt;As identity professionals who care about that delicate balance between user experience and security, the question that comes up time and again is this: what is the best way to secure these apps? How do I ensure a slick user authentication process at the start and then safely re-use that session across all aspects of my application, when some parts of it are delivered via browser or web-view? In this article, I’ll explore the challenges that these hybrid app models present to us and propose a workable solution based on modern identity standards (with a sprinkling of identity orchestration). The solution I describe below has been built and tested using &lt;strong&gt;Ping Identity’s &lt;a href=&quot;https://docs.pingidentity.com/pingone/getting_started_with_pingone/p1_p1sso_start.html&quot;&gt;PingOne SSO&lt;/a&gt;&lt;/strong&gt; identity provider together with the &lt;a href=&quot;https://docs.pingidentity.com/davinci/davinci_landing_page.html&quot;&gt;&lt;strong&gt;PingOne DaVinci&lt;/strong&gt; identity orchestration engine&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Eager for the punchline? &lt;a href=&quot;#tldr&quot;&gt;Here’s the succinct summary&lt;/a&gt; for those on a time and attention budget!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The session transfer problem&lt;/h2&gt;
&lt;p&gt;Before we can start to solve the challenges that we face in securing hybrid mobile applications, we should start by better defining the problem space. For the purposes of this discussion, we define a &lt;strong&gt;hybrid mobile application&lt;/strong&gt; as a native application running on a mobile device where certain portions of the application’s functionality are delivered via a web browser or web view on the device — that is to say, the app is not fully native. The exact specifics of how and when the app switches context between a native delivery versus a browser-based delivery of functionality are not important, however there are two requirements that we must fulfil, namely&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;access to the browser-based content requires an authenticated user session (presumably in the form of a browser session cookie, which is how these things have always been done)&lt;/li&gt;
&lt;li&gt;the user has already performed authentication in order to access the app itself and as such should not be prompted to re-authenticate in order to access the browser-based content.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A number of follow-on points are salient here and those who have grappled with this situation in the past are no doubt familiar with them. The most important ones are, perhaps, as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In a native application context, the authenticated user session is typically represented by an OAuth access token, issued to the application by a trusted Authorisation Server in response to a user login.&lt;/li&gt;
&lt;li&gt;There are &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8252&quot;&gt;established best practices&lt;/a&gt; for how a native application should go about obtaining such a token by leveraging the Auth Code Flow with PKCE via the system browser. This approach is battle-hardened and peer-reviewed and should always be the first alternative considered when building a user authentication process for a native app.&lt;/li&gt;
&lt;li&gt;To directly contradict the previous point, however, many (if not most) digital product owners and UX designers find the experience trade-off unacceptable and thus strongly resist using a browser-delivered authentication ceremony within the context of a native application, preferring to use fully native SDKs to interact directly with the authentication service to obtain the necessary access token.&lt;/li&gt;
&lt;li&gt;Whichever approach is selected for initial user authentication, the basic requirement is the same in terms of providing a single-sign-on experience if the app includes hybrid web content; in order for the user to access said content without re-authentication, there needs to be a session cookie present in the browser at the time that content is requested. This is the most basic requirement (the &lt;em&gt;sine qua non&lt;/em&gt;, if you will) without which the user is guaranteed to be prompted for repeat authentication&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All this talk of cookies is possibly making you hungry by now, but we do need to explore the intricacies just a little deeper before taking a well-earned coffee break! This problem really is all about cookies, though — that much we cannot deny. One would think and would hope that the pattern of using the system browser to obtain authentication credentials from the user in the first place would create a session cookie in that browser as a serendipitous side effect (and give us a further piece of ammunition in convincing our product owner and UX colleagues to eschew a fully-native alternative). In practice, however, this approach has been shown to be non-deterministic at best, if not downright flaky, due to the somewhat arcane and seemingly ever-changing rules regarding cookie persistence and visibility across browser tabs and invocations. Without going into all of the details, I think it is sufficient to say that, based on the experiences of many doing this work in the field today, we cannot assume that a session cookie set during initial login will be available during subsequent browser-based interactions. The reasons for this are all very well-meant; examples being Apple’s &lt;a href=&quot;https://www.avenga.com/magazine/intelligent-tracking-prevention/&quot;&gt;Intelligent Tracking Prevention&lt;/a&gt; feature and Google’s equivalent Privacy Sandbox work — designed to increase user privacy. As a result, even if we are lucky enough to stumble upon a configuration on a given device that does give us the cookie visibility that we need, there is every chance that further tightening of cookie rules in the future could break our solution, leaving both product owners and (more importantly) end users furious.&lt;/p&gt;
&lt;p&gt;What we are left with is a challenge to solve, broadly defined as follows:&lt;/p&gt;
&lt;p&gt;“how, then, do we engineer a mechanism where the access token that represents the logged-in user within the native context can be used to create a browser session cookie at the point that the user needs to access web content”&lt;/p&gt;
&lt;p&gt;It goes without saying that any such mechanism MUST be secure and avoid exposing us and our users to malicious theft of tokens, user sessions and the applications and API’s that these protect.&lt;/p&gt;
&lt;h2&gt;A standards-based pattern for secure session transfer&lt;/h2&gt;
&lt;p&gt;We know that we need a mechanism whereby we can take the app’s representation of an authenticated user session, the OAuth access token, and turn this into an authenticated session in a web browser, in the form of a session cookie. OAuth does not provide a standardised pattern or grant type that we can use to solve this problem and as a result a number of approaches have emerged, many of which fall down on either user experience or security, and sometimes both.&lt;/p&gt;
&lt;p&gt;A naive approach to the problem would be to launch a new OAuth authorisation request in the browser via the Auth Code flow, and simply pass the app’s access token directly to the Authorisation Server as a custom URL parameter. This does imply that whatever OAuth Server implementation is in use must provide the capability to extract the access token from the query string, validate it and create a web session cookie for the subject of the token. While not many implementations that I’ve encountered will provide this sort of “access-token-to-web-session” mechanism out of the box, most will offer some sort of customisation capability, via plug-in or extension script that will allow such a mechanism to be built.&lt;/p&gt;
&lt;p&gt;I would advise against this approach for a number of reasons, with the most critical being the security risk that it carries.&lt;/p&gt;
&lt;p&gt;We must remember, at all times, the inherent security challenge implied by the Bearer Token model, where simple possession of an access token grants whoever holds the token full power to use it. We should assume that any access token issued to a native application client would be a powerful artefact indeed and allow a malicious actor the ability to invoke any API on the application back-end should they manage to obtain it. &lt;strong&gt;Adding such a dangerous object to a URL query string and sending it out “into the wild” where it could almost trivially be intercepted is not the solution we seek.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The alternative approach that I recommend is to use a relatively-recent addition to the OAuth family of standards to achieve the same outcome in a way that provides better protection of the access token during the session transfer process, namely OAuth 2.0 Pushed Authorization Requests (or PAR), described in &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9126&quot;&gt;RFC 9126&lt;/a&gt;. To set the scene, let’s look at the abstract of the document that describes the approach:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“This document defines the pushed authorization request (PAR) endpoint, which allows clients to push the payload of an OAuth 2.0 authorization request to the authorization server via a direct request and provides them with a request URI that is used as reference to the data in a subsequent call to the authorization endpoint.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To paraphrase and simplify, PAR allows us to provide our sensitive access token directly to the Authorisation Server via a direct HTTPS Post from our native app code, rather than including it as an insecure query string parameter in the browser. This separation of data payloads between the “back channel” (direct POST) and the “front channel” (browser GET) significantly improves the security baseline of what we need to achieve and provides solid scaffolding for the further work we need to do.&lt;/p&gt;
&lt;p&gt;An additional advantage to the approach is that PAR gives us a ready-made query string parameter, in the form of the returned &lt;em&gt;request_uri&lt;/em&gt;, that is guaranteed to be single-use and short-lived, thus reducing the very large attack surface that the alternative approach inherently creates.&lt;/p&gt;
&lt;p&gt;I mentioned further work above, and it is important to realise that while PAR does provide a great starting point, it is not the complete solution to this challenge by itself. There is more to be done both in terms of user experience and also security, which we’ll explore in the next section. For now, though, let’s describe the “bones” of the approach here, in the form of a worked example&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Alice opens the Acme Inc native application and clicks the “Login” button. She is authenticated via a native authentication experience (no browser pop-up) that results in a valid OAuth access token being sent to the app. The app responsibly and securely stores this access token on her behalf.&lt;/li&gt;
&lt;li&gt;Alice clicks the “Manage My Profile” button in the app. Acme Inc delivers profile management via an embedded browser view within the app, and the web backend that serves this content requires a session cookie in order to identify Alice and link her to the correct profile.&lt;/li&gt;
&lt;li&gt;When Alice clicks the button, native app code retrieves her access token and initiates a secure POST to the Acme Inc Authorisation Server’s PAR Endpoint. The post body includes access token as a custom parameter, in addition to client_id, scope and all the other required OAuth parameters. In accordance with the PAR standard, the Authorisation server returns a response including a &lt;em&gt;request_uri&lt;/em&gt;, a single use value that acts as a reference to the request data that has been provided.&lt;/li&gt;
&lt;li&gt;The native app code now opens a browser window, passing in the URL of the Acme Inc Authorisation Server’s authentication endpoint, with the &lt;em&gt;request_uri&lt;/em&gt; from the previous step as a parameter. The Authorisation Server uses identity orchestration to validate the provided access token and create a web session for Alice, before redirecting her to the profile management application, where the browser cookie now ensures a single-sign-on experience&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The informed reader will, of course, have many remaining questions based on the above explanation. Let’s address them now.&lt;/p&gt;
&lt;h2&gt;Enabling secure session transfer on top of PAR&lt;/h2&gt;
&lt;p&gt;Native applications are public clients from an OAuth point of view. Put another way using language extracted directly from &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6749&quot;&gt;RFC6749&lt;/a&gt;, &lt;em&gt;“it is assumed that any client authentication credentials included in the application can be extracted”&lt;/em&gt;. The implications of this reality are far reaching and many of them beyond the scope of this discussion, but it matters here because it means we cannot use a confidential client (with a client secret) to kick off our PAR request from inside the app.&lt;/p&gt;
&lt;p&gt;It is important to realise here that we are not using PAR for its ability to complete an end-to-end flow, that ends with a redirection and ultimate token issuance to the same OAuth client that initiated the request. We are rather using PAR as a way to initiate a browser flow and then build on the side effect of the session cookie that is created. This means that we do not, in fact, want to ever complete the flow that we start via PAR. We only want to go as far as creating a session cookie before we break out and redirect the user to the actual application, which will start its own Auth Code flow from scratch, using its own client_id, redirect_uri, PKCE enforcement, etc.&lt;/p&gt;
&lt;p&gt;The combination of the two factors above suggests that we should configure and use a specific OAuth Client as a sort of session transfer proxy — that is, a completely new client unrelated to either the native app that starts the transfer or the web app that ultimately benefits. We should take as many precautions as we can to lock down this session transfer client such that it can only be used for a single purpose, but also we should be sure not to add any configurations to our existing clients that could weaken their existing security posture.&lt;/p&gt;
&lt;p&gt;Let’s describe the flow again, adding the additional details that we have described above.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Alice clicks the “Manage my profile” button in the app. The app code starts the PAR process using the client_id of the dedicated Session Transfer Client. It includes a scope such as “session_transfer” in the request, and generates a PKCE Code Challenge that it includes with the request. Note that the corresponding Code Verifier can be discarded at this point since this flow is not intended to ever result in an access token issuance.&lt;/li&gt;
&lt;li&gt;The app code includes three other pieces of information (at minimum). These are the access token that it already holds for the user, the IP address that it is using, and the URL of the profile management application.&lt;/li&gt;
&lt;li&gt;It sends the PAR request to the authorisation server and obtains a response that contains a request_uri, which it appends to the AS’s authorization endpoint URL before opening a browser tab to initiate the front-channel flow.&lt;/li&gt;
&lt;li&gt;When the front-channel request hits the Authorisation Server, the request is validated (to ensure it has not expired or already been used) before being handed to the identity orchestration engine for further processing.&lt;/li&gt;
&lt;li&gt;The orchestration engine (as above, I have tested this with PingOne DaVinci, mileage may vary if using a different platform) must introspect or validate the provided access token to ensure that it is valid, has not expired and was indeed issued to the expected client (the mobile app). It should also compare the IP address provided as part of the PAR request to the IP address passed via the current HTTP request, to ensure that the backend and frontend requests come from the same place. Further checks can be implemented using orchestration logic as required.&lt;/li&gt;
&lt;li&gt;Should the above checks succeed, the orchestration engine extracts the “sub” claim from the access token to determine the appropriate user, and sets a session cookie in the browser for that same user.&lt;/li&gt;
&lt;li&gt;At this point, the orchestration logic simply redirects the browser to the URL of the profile management application (the same URL that was provided via the PAR back-channel initiation in step 3). This is the step that interrupts the flow started by the Session Transfer Client and ensure that no auth code or access token is ever issued.&lt;/li&gt;
&lt;li&gt;The profile management application now receives the browser redirect and is responsible for initiating its own OAuth/OpenID Connect Auth Code flow, just as it would had a browser opened the app URL directly. It should generate its own redirect to the AS, including its own client_id and redirect_uri, its own appropriate scopes and its own PKCE Code Challenge. The AS will start a user authentication process that should complete with no further user interaction required, based on the presence of the session cookie set in step 6.&lt;/li&gt;
&lt;li&gt;The browser is thus redirected back to the profile management application with everything that is needed to display logged-in user content.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;My good friend and proof-reader &lt;a href=&quot;https://medium.com/@gransomeuk&quot;&gt;Gransomeuk&lt;/a&gt; pointed out that some folks will just want the take away, rather than the full three course meal. With his help, here’s the summary, with a nifty sequence diagram to match.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phase 1 (Back-Channel):&lt;/strong&gt; The mobile app passes the highly sensitive access token directly to the Authorization Server over a secure connection (POST). In return, it gets a short-lived reference string (&lt;code&gt;request_uri&lt;/code&gt;). This solves the problem of putting the Access Token in a visible URL query string.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 2 (Front-Channel):&lt;/strong&gt; The app opens the browser using only the reference string. The Identity Orchestration engine (e.g., PingOne DaVinci) verifies that the person in the browser is the same person who holds the app token (by checking IP addresses, etc) and drops a session cookie in the browser.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 3 (SSO):&lt;/strong&gt; The browser is redirected to the target Web Application. Because the cookie now exists, the Web App can sign the user in automatically via standard OpenID Connect without asking the user to type in a password again.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Example sequence diagam — App-to-web session transfer with PAR&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;872&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/sequence-diagram.DMnuZZu0_ZIKT4N.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/sequence-diagram.DMnuZZu0_ZdXTVV.webp 640w, https://developer.pingidentity.com/blog/_astro/sequence-diagram.DMnuZZu0_q2ByF.webp 750w, https://developer.pingidentity.com/blog/_astro/sequence-diagram.DMnuZZu0_1uByiC.webp 828w, https://developer.pingidentity.com/blog/_astro/sequence-diagram.DMnuZZu0_ZKVGEE.webp 1080w, https://developer.pingidentity.com/blog/_astro/sequence-diagram.DMnuZZu0_ZIKT4N.webp 1100w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;“Next time on…”&lt;/h2&gt;
&lt;p&gt;In the interests of keeping things digestible, I will publish two follow up articles in the coming weeks.&lt;/p&gt;
&lt;p&gt;The first will &lt;a href=&quot;https://developer.pingidentity.com/blog/implementing-secure-session-transfer-a-field-guide-using-the-pingone-platform/&quot;&gt;explore the technical implementation&lt;/a&gt; of this pattern end-to-end, using PingOne SSO and PingOne DaVinci, together with a simple Android application to show it all working.&lt;/p&gt;
&lt;p&gt;The second will dive a little deeper into some of the security implications of this approach, highlight the advantages, point out a number of inherent drawbacks and also suggest further steps that can be taken to harden the implementation.&lt;/p&gt;
&lt;h2&gt;References and further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8252&quot;&gt;RFC 8252 — OAuth 2.0 for Native Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.researchgate.net/publication/363052475_COOKIEGRAPH_Measuring_and_Countering_First-Party_Tracking_Cookies&quot;&gt;Urban et al., “Beyond the Front Page: Measuring and Countering First-Party Cookie Tracking,” in Proc. of the 2020 World Wide Web Conference (WWW).&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9126&quot;&gt;RFC 9126 — OAuth 2.0 Pushed Authorization Requests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.CiXag4ms_27rSQ8.webp" medium="image" type="image/webp" width="2386" height="1256"/></item><item><title>Securing MCP Servers with Ping’s MCP Gateway</title><link>https://developer.pingidentity.com/blog/securing-mcp-servers-with-pings-mcp-gateway/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/securing-mcp-servers-with-pings-mcp-gateway/</guid><description>Building MCP servers that expose your business logic to AI agents? Discover how PingGateway can act as your security bouncer, handling OAuth validation, auditing, rate limiting, and policy enforcement without adding a single line of security code to your servers.</description><pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You’ve been shipping &lt;a href=&quot;https://modelcontextprotocol.io/docs/getting-started/intro&quot;&gt;&lt;strong&gt;MCP servers&lt;/strong&gt;&lt;/a&gt; and each one comes with its own OAuth handler, its own scope-checking logic, its own audit hooks. Security code that should be administered centrally ends up copy-pasted across every server you build.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.pingidentity.com/pinggateway/latest/mcp/&quot;&gt;&lt;strong&gt;MCP security gateway&lt;/strong&gt;&lt;/a&gt; capabilities in &lt;a href=&quot;https://www.pingidentity.com/en/product/pinggateway.html&quot;&gt;&lt;strong&gt;PingGateway&lt;/strong&gt;&lt;/a&gt; solve this by acting as a security bouncer in front of your MCP servers. The bouncer checks credentials at the door, enforces the rules, and logs everything — so your servers never have to.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can find a deeper dive into MCP’s architecture, workflow, and security model in the &lt;a href=&quot;https://developer.pingidentity.com/identity-for-ai/agents/idai-what-is-mcp.html&quot;&gt;Ping Identity MCP documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Where PingGateway sits as your MCP security gateway&lt;/h2&gt;
&lt;p&gt;In a typical setup, &lt;strong&gt;PingGateway as an MCP security gateway&lt;/strong&gt; sits between the MCP client (the AI agent) and your MCP server.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;MCP Gateway logical component diagram showing PingGateway positioned between the MCP client and MCP server, intercepting all requests to enforce authentication, authorization, and operational controls before they reach the server.&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;540&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/mcp-gateway.BVCrSNwq_zPX73.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/mcp-gateway.BVCrSNwq_1j6A6e.webp 640w, https://developer.pingidentity.com/blog/_astro/mcp-gateway.BVCrSNwq_AwQCL.webp 750w, https://developer.pingidentity.com/blog/_astro/mcp-gateway.BVCrSNwq_25KVVm.webp 828w, https://developer.pingidentity.com/blog/_astro/mcp-gateway.BVCrSNwq_zPX73.webp 960w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;What PingGateway adds for MCP servers&lt;/h2&gt;
&lt;p&gt;By fronting your MCP server, PingGateway validates that requests are syntactically correct MCP, enforces operational requirements like audit and throttling, enforces authorization of the request, and mediates security tokens before forwarding to the MCP server.&lt;/p&gt;
&lt;p&gt;Your server continues to implement tools and domain behavior; the gateway provides the surrounding security and operational controls.&lt;/p&gt;
&lt;h2&gt;What capabilities does PingGateway provide?&lt;/h2&gt;
&lt;p&gt;PingGateway extends its proven API security stack with &lt;strong&gt;MCP-aware capabilities&lt;/strong&gt; you configure once and reuse across every server:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;McpValidationFilter&lt;/strong&gt;: Validates &lt;strong&gt;Origin&lt;/strong&gt; / &lt;strong&gt;Accept&lt;/strong&gt; headers and MCP / JSON‑RPC format and populates an &lt;strong&gt;McpContext&lt;/strong&gt; so subsequent filters (throttling, policy, metrics) can inspect method and tool names.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;McpProtectionFilter&lt;/strong&gt;: Serves &lt;strong&gt;OAuth 2.0 Protected Resource Metadata&lt;/strong&gt; (&lt;code&gt;/.well-known/oauth-protected-resource/...&lt;/code&gt;) on behalf of your MCP endpoint, ensures the access token’s &lt;strong&gt;&lt;code&gt;aud&lt;/code&gt;&lt;/strong&gt; matches the configured &lt;code&gt;resourceId&lt;/code&gt; and uses PingGateway’s &lt;strong&gt;OAuth 2.0 resource server&lt;/strong&gt; capabilities to validate tokens and enforce scopes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;McpAuditFilter:&lt;/strong&gt; Emits &lt;strong&gt;MCP‑specific audit events&lt;/strong&gt; (who called which tool, where, and with what result)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP metrics&lt;/strong&gt;: Exposes &lt;strong&gt;Prometheus metrics&lt;/strong&gt; per MCP method/tool (counts, latencies, errors).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP throttling&lt;/strong&gt;: Rate limits access per route, per tool, per client or per identity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Seeing it in action: the “unauthenticated” request&lt;/h2&gt;
&lt;p&gt;The gateway’s job is to stop what shouldn’t get through — and wave through what should. If the MCP client (AI agent) sends a request without the proper identity tokens required by the Ping Identity platform, the gateway will block it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The situation: missing authorization header.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An AI agent sends a tool call without an &lt;code&gt;Authorization&lt;/code&gt; header:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;POST /mcp/v1/call_tool&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;method&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tools/call&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;params&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;access_customer_database&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;arguments&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;customer_id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;12345&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; &lt;code&gt;401 Unauthorized&lt;/code&gt; — rejected by PingGateway before the request ever reaches your server.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;With valid credentials, the gateway allows the request through.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The same agent retries with a valid, scoped Bearer token:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt; /mcp/v1/call_tool&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Authorization&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Bearer &amp;lt;valid-access-token&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;method&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tools/call&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;params&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;access_customer_database&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;arguments&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;customer_id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;12345&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; &lt;code&gt;200 OK&lt;/code&gt; — PingGateway validates the token, confirms scope, logs the event, and forwards the request to your MCP server. Your server never saw the first request at all.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Benefits for the MCP server developer&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MCP security shouldn’t be implemented in each MCP server’s codebase&lt;/strong&gt;&lt;br /&gt;
Without a gateway, each MCP server grows its own OAuth, scopes, auditing, throttling, and risk logic.&lt;br /&gt;
With MCP Security Gateway, a &lt;strong&gt;standard PingGateway route&lt;/strong&gt; in front of each MCP server centralizes that logic; you reuse the pattern for every new server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Comply with MCP authorization by configuration, not code&lt;/strong&gt;&lt;br /&gt;
MCP requires &lt;strong&gt;OAuth 2.0 Protected Resource Metadata&lt;/strong&gt; so agents know which AS to use and which resource they call. PingGateway serves this metadata and validates tokens according to it, so you configure security once instead of coding it per server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gain observability “for free”&lt;/strong&gt;&lt;br /&gt;
Audit logs and metrics at the gateway show &lt;strong&gt;which tools are used, by whom, and how often&lt;/strong&gt;, without extra instrumentation in your MCP code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protect MCP servers from noisy or malicious agents&lt;/strong&gt;&lt;br /&gt;
Central throttling and policy/risk rules ensure your servers only see pre‑validated, rate‑limited, authorized requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrate legacy or sensitive backends cleanly&lt;/strong&gt;&lt;br /&gt;
Use &lt;strong&gt;token mediation&lt;/strong&gt; at the gateway to map agent tokens to whatever your backends expect (different scopes, JWT formats, API keys, mTLS), keeping your MCP server’s backend calls simple and aligned with existing security models.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The gateway acts as the “bouncer,” ensuring the server only spends resources processing requests that are properly formatted, authenticated, and authorized.&lt;/p&gt;
&lt;h2&gt;Get started!&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.pingidentity.com/pinggateway/latest/mcp/&quot;&gt;MCP security gateway tutorial&lt;/a&gt; walks you through the complete setup. Download the sample MCP agent and server (or bring your own), insert PingGateway in front of your MCP server, and enable MCP processing and authorization in the gateway configuration.&lt;/p&gt;
&lt;p&gt;MCP exposes your business capabilities to AI agents. &lt;a href=&quot;https://www.pingidentity.com/en/product/pinggateway.html&quot;&gt;&lt;strong&gt;PingGateway&lt;/strong&gt;&lt;/a&gt; makes sure only the right agents — with the right credentials and the right permissions — ever reach them.&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Get started with the &lt;a href=&quot;https://docs.pingidentity.com/pinggateway/latest/mcp/&quot;&gt;MCP security gateway tutorial&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Visit the &lt;a href=&quot;https://developer.pingidentity.com/identity-for-ai/agents/idai-what-is-mcp.html&quot;&gt;MCP documentation on the Ping Identity developer portal&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Check out developer resources for all Ping Identity products at &lt;a href=&quot;https://developer.pingidentity.com/&quot;&gt;developer.pingidentity.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity Developer Community&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.1PUsVrVE_Z16vVkj.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>We Rebuilt the PingOne API Docs. Here’s What’s New.</title><link>https://developer.pingidentity.com/blog/we-rebuilt-the-pingone-api-docs/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/we-rebuilt-the-pingone-api-docs/</guid><description>A walkthrough of the new PingOne API docs experience, including faster navigation, in-browser request testing, and smarter search.</description><pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;If you build integrations, you probably spend a lot of time staring at API documentation. So when we decided to overhaul the PingOne API docs, our goal was simple: get out of your way and make it easier for you to actually test and write code.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/&quot;&gt;new site&lt;/a&gt; officially went live in early January. Here’s a quick rundown of what changed and how to use the new features.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The PingOne API documentation home page&quot; loading=&quot;lazy&quot; width=&quot;2726&quot; height=&quot;1326&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/api-documentation-home-page.Btw-8crt_Z219N0d.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/api-documentation-home-page.Btw-8crt_Z1X3JnR.webp 640w, https://developer.pingidentity.com/blog/_astro/api-documentation-home-page.Btw-8crt_ZL8foD.webp 750w, https://developer.pingidentity.com/blog/_astro/api-documentation-home-page.Btw-8crt_2bSQ2j.webp 828w, https://developer.pingidentity.com/blog/_astro/api-documentation-home-page.Btw-8crt_1O2XBx.webp 1080w, https://developer.pingidentity.com/blog/_astro/api-documentation-home-page.Btw-8crt_Z1Tbh3k.webp 1280w, https://developer.pingidentity.com/blog/_astro/api-documentation-home-page.Btw-8crt_1wKL7V.webp 1668w, https://developer.pingidentity.com/blog/_astro/api-documentation-home-page.Btw-8crt_PuGJf.webp 2048w, https://developer.pingidentity.com/blog/_astro/api-documentation-home-page.Btw-8crt_4lUyr.webp 2560w, https://developer.pingidentity.com/blog/_astro/api-documentation-home-page.Btw-8crt_Z219N0d.webp 2726w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;It’s a lot faster and easier to navigate&lt;/h2&gt;
&lt;p&gt;The most obvious improvement is performance. The site loads faster, the pages are lighter, and the layout now matches the rest of the &lt;a href=&quot;https://docs.pingidentity.com&quot;&gt;Ping Identity documentation&lt;/a&gt; ecosystem.&lt;/p&gt;
&lt;p&gt;We also reorganized the navigation so you don’t have to dig through endless menus to find helpful information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;New to PingOne?&lt;/strong&gt; We have plenty of resources to help you get up and running quickly. Jump into our &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/getting-started/&quot;&gt;&lt;em&gt;Getting Started&lt;/em&gt;&lt;/a&gt; guide, explore &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/foundations/&quot;&gt;&lt;em&gt;Foundations&lt;/em&gt;&lt;/a&gt;, or follow along with step-by-step tutorials in the &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/workflow-library/&quot;&gt;&lt;em&gt;Use Case Library&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learn about APIs:&lt;/strong&gt; You can dive straight into the PingOne Platform &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/platform/&quot;&gt;SSO&lt;/a&gt; or &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/auth/&quot;&gt;Auth&lt;/a&gt; APIs to see detailed info on all endpoints, which are logically grouped by functional area.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Universal services:&lt;/strong&gt; These now have their own dedicated section, making it clearer how they fit into the wider Ping Identity portfolio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smart search:&lt;/strong&gt; If you still can’t find what you need, the search bar now uses the same AI-assisted backend as our other &lt;a href=&quot;https://docs.pingidentity.com&quot;&gt;doc sites&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Test endpoints in your browser&lt;/h2&gt;
&lt;p&gt;For every endpoint, you’ll see example requests and responses in multiple programming languages. More importantly, we completely rebuilt the &lt;strong&gt;Try Request&lt;/strong&gt; feature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Live testing:&lt;/strong&gt; Plug in your parameters, hit the button, and get real API responses back immediately.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistent parameters:&lt;/strong&gt; You no longer have to copy and paste the same environment or user IDs over and over. Your parameters carry over from one API call to the next. This includes access tokens, so you don’t have to constantly re-authenticate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure local storage:&lt;/strong&gt; To keep things safe, those variables only live in your web browser’s session storage. The second you close the tab, they’re gone.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Create User API end point example page&quot; loading=&quot;lazy&quot; width=&quot;2726&quot; height=&quot;1326&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/create-user-api-example.pPV8j_9Y_15bJBg.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/create-user-api-example.pPV8j_9Y_ZwksNc.webp 640w, https://developer.pingidentity.com/blog/_astro/create-user-api-example.pPV8j_9Y_Z1PFvSE.webp 750w, https://developer.pingidentity.com/blog/_astro/create-user-api-example.pPV8j_9Y_Z2entHG.webp 828w, https://developer.pingidentity.com/blog/_astro/create-user-api-example.pPV8j_9Y_Z1ScQKY.webp 1080w, https://developer.pingidentity.com/blog/_astro/create-user-api-example.pPV8j_9Y_Z2g7NRX.webp 1280w, https://developer.pingidentity.com/blog/_astro/create-user-api-example.pPV8j_9Y_2t8yt1.webp 1668w, https://developer.pingidentity.com/blog/_astro/create-user-api-example.pPV8j_9Y_Z1zjBkE.webp 2048w, https://developer.pingidentity.com/blog/_astro/create-user-api-example.pPV8j_9Y_22y2wf.webp 2560w, https://developer.pingidentity.com/blog/_astro/create-user-api-example.pPV8j_9Y_15bJBg.webp 2726w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;Update your bookmarks&lt;/h2&gt;
&lt;p&gt;To keep things simple, all developer-focused content, including cloud and on-premise APIs, now lives in one place: the &lt;a href=&quot;https://developer.pingidentity.com&quot;&gt;Developer Portal&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check out the new PingOne API docs at &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/&quot;&gt;&lt;strong&gt;developer.pingidentity.com/pingone-api&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Let us know what you think&lt;/h2&gt;
&lt;p&gt;This launch was a massive effort, but the work doesn’t stop here. Documentation is never really “finished.”&lt;/p&gt;
&lt;p&gt;Give the new site a spin. If you run into a weird bug, see a typo, or have any suggestions, let us know by clicking the feedback icon in the top-right corner of each page.&lt;/p&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Visit our new &lt;a href=&quot;https://developer.pingidentity.com/pingone-api/&quot;&gt;PingOne API documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Check out developer resources for all Ping Identity products at &lt;a href=&quot;https://developer.pingidentity.com/&quot;&gt;developer.pingidentity.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Join the discussion on the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity Developer Community&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.BOqoeYJk_ZRA2Fe.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Introducing the PingOne Advanced Identity Cloud (AIC) MCP Server</title><link>https://developer.pingidentity.com/blog/introducing-the-aic-mcp-server/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/introducing-the-aic-mcp-server/</guid><description>Introducing the AIC MCP Server: Connect Your Identity Infrastructure to AI Agents.</description><pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Developers have already discovered that LLMs (such as Claude, Gemini, GPT, Grok, and others) accelerate workflows across coding, debugging, and operations. A real breakthrough has been the ability to create custom agents within the IDE. Now developers can write specialized agent workflows that combine reasoning, tooling, and domain logic to solve problems autonomously.&lt;/p&gt;
&lt;p&gt;To help these agents work effectively, we need a bridge between the AI and your actual services. That is where &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;p&gt;Today, we are excited to introduce the &lt;strong&gt;AIC MCP Server&lt;/strong&gt;. This tool connects your PingOne Advanced Identity Cloud (AIC) environment directly to the AI agents you use every day in IDEs like Cursor, VS Code, and GitHub Copilot, or command-line tools like Claude Code and Gemini CLI.&lt;/p&gt;
&lt;h2&gt;What Exactly is an MCP Server?&lt;/h2&gt;
&lt;p&gt;Think of the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; as a universal remote for AI. In the past, every AI platform had to build its own unique way to talk to different databases or services. MCP changes that by creating a standard “contract.”&lt;/p&gt;
&lt;p&gt;It allows an AI agent to see a list of available tools, understand what they do, and know exactly how to call them. When you use an MCP server, you aren’t just giving an AI a window into your data; you’re giving it a specialized “hand” to perform tasks for you.&lt;/p&gt;
&lt;p&gt;You can find a more detailed look into MCP’s architecture, workflow, and security model in the &lt;a href=&quot;https://developer.pingidentity.com/identity-for-ai/agents/idai-what-is-mcp.html&quot;&gt;Ping Identity MCP documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;How the AIC MCP Server Works&lt;/h2&gt;
&lt;p&gt;PingOne Advanced Identity Cloud (AIC) is Ping Identity’s modern, single-tenant identity platform. It manages users, authentication flows, roles, policies, audit logs, and customer-facing themes. If you’re running an identity operation, AIC is where the configuration lives.&lt;/p&gt;
&lt;p&gt;The AIC MCP server is an open-source TypeScript tool, distributed through npm, which exposes 40+ tools that wrap AIC’s REST APIs and packages them as MCP-compatible functions. Instead of manually navigating the console or hand-crafting API requests, you describe what you want in natural language. Your agent translates that to the right MCP tools, the MCP server handles the API calls and authentication, and returns results that the agent can reform into natural language.&lt;/p&gt;
&lt;p&gt;Here’s the practical difference:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Task&lt;/th&gt;&lt;th&gt;Without MCP&lt;/th&gt;&lt;th&gt;With AIC MCP Server&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Find failed logins from 9 PM – 11 PM yesterday&lt;/td&gt;&lt;td&gt;20 minutes (console navigation, log filtering, export)&lt;/td&gt;&lt;td&gt;1 minute (“Show me authentication failures from 9 PM – 11 PM yesterday”)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Create a new role with specific permissions&lt;/td&gt;&lt;td&gt;15 minutes (console form, multi-step workflow)&lt;/td&gt;&lt;td&gt;2 minutes (“Create a role called product-admin with these permissions: …”)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;List all users in a specific group&lt;/td&gt;&lt;td&gt;10 minutes (API docs, construct request, parse response)&lt;/td&gt;&lt;td&gt;30 seconds (“Which users are in the engineers group?”)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Why This Matters: MCP Adoption Is Evolving&lt;/h2&gt;
&lt;p&gt;MCP has become the standard for AI-to-infrastructure integration. Popular developer tools (such as Claude Desktop, Cursor, GitHub Copilot, VS Code, Gemini CLI) all now support MCP. Developers are moving from being software engineers or technical admins to being agent orchestrators.&lt;/p&gt;
&lt;p&gt;Identity and access management (IAM) is a perfect fit for AI assistance. Configuration can be repetitive. Audits demand detailed log parsing. Bulk operations can be tedious. Configuration updates may take a while when navigating multiple screens and modals in the web console. IAM is also not an isolated capability, often connected to other systems that benefit from being managed as a group. In this way, MCP clients allow connection of multiple MCP servers from different providers to bridge multi-service, multi-product workflows. The more systems connected, the more autonomy an agent has, the more the user can delegate.&lt;/p&gt;
&lt;p&gt;For developers, this means faster operations. Less manual console navigation. Less copying and pasting IDs between tabs. Reduced context-switching.&lt;/p&gt;
&lt;h2&gt;Core Capabilities: 35+ Tools Across Five Categories&lt;/h2&gt;
&lt;p&gt;The AIC MCP server exposes 35+ tools organized into five functional areas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Managed Objects:&lt;/strong&gt; Perform “search, create, and update” tasks for users, roles, groups, and organizations. (for example, &lt;em&gt;“Find all users with an @acme.com email.”&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Themes &amp;amp; Branding:&lt;/strong&gt; Update your login pages, logos, and brand colors across different regions programmatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging &amp;amp; Audits:&lt;/strong&gt; Query activity logs by time, user, or error type—perfect for quick security check-ups.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secrets &amp;amp; Variables:&lt;/strong&gt; Securely manage API keys and environment variables without having to hard-code them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication Journeys:&lt;/strong&gt; Build and test complex login flows (journeys) and the scripts that power them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The server exposes many tools providing broad capability, with every action authenticated using OAuth 2.0 with PKCE and tied to your user identity in the audit log.&lt;/p&gt;
&lt;h2&gt;Getting Started: Hands-On Walkthrough&lt;/h2&gt;
&lt;p&gt;Getting the AIC MCP Server running takes 5 minutes. You’ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Sandbox or Development AIC tenant.&lt;/li&gt;
&lt;li&gt;Node.js 18+ on your machine (or Docker).&lt;/li&gt;
&lt;li&gt;An MCP-compatible AI client (Claude Desktop is recommended).&lt;/li&gt;
&lt;li&gt;5 minutes of setup time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Step 1: Configure Your MCP Client&lt;/h3&gt;
&lt;p&gt;Find your AIC tenant’s base URL in the console (for example, &lt;code&gt;my-tenant.forgeblocks.com&lt;/code&gt;). Then add the configuration to your MCP client’s config file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;mcpServers&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;aic-mcp-server&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;stdio&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;command&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;npx&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;args&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;-y&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;@ping-identity/aic-mcp-server&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;env&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;AIC_BASE_URL&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;your-tenant.forgeblocks.com&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;your-tenant.forgeblocks.com&lt;/code&gt; with your actual tenant URL (no &lt;code&gt;https://&lt;/code&gt; prefix).&lt;/p&gt;
&lt;h3&gt;Step 2: Authenticate&lt;/h3&gt;
&lt;p&gt;Restart your AI client. When you first use a tool, the server will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open your browser to your AIC login screen.&lt;/li&gt;
&lt;li&gt;Ask you to log in with your AIC admin credentials as an administrator.&lt;/li&gt;
&lt;li&gt;Show you the permissions the server needs (scopes like &lt;code&gt;fr:idm:\*&lt;/code&gt;, &lt;code&gt;fr:idc:monitoring:\*&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;After you’re authenticated, securely save your token in your OS keychain (where available).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a one-time setup for the session. The token stays in your keychain and can be reused until expiry or ephemerally if using the Docker image. Subsequent tool calls reuse the cached token, if it is still valid.&lt;/p&gt;
&lt;h3&gt;Step 3: Make Your First Tool Call&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Count all users in the system and show me the total.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Behind the scenes, your agent will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand the available tools provided by the MCP server.&lt;/li&gt;
&lt;li&gt;Call the &lt;code&gt;listManagedObjects&lt;/code&gt; tool to discover object types.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;queryManagedObjects&lt;/code&gt; to fetch users.&lt;/li&gt;
&lt;li&gt;Return the count in plain English.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Expected response: “There are 247 users in your AIC tenant.”&lt;/p&gt;
&lt;h2&gt;Real-World Use Case: Debugging Authentication Issues&lt;/h2&gt;
&lt;p&gt;It’s 8 AM. A high-priority bug report just landed: “A recent change to the primary user authentication journey has caused failures in overnight tests.” You need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Analyze the bug&lt;/strong&gt; and map it to the existing auth journey.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Provision test identities&lt;/strong&gt; that match the specific failure criteria.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trace the logs&lt;/strong&gt; to find the exact line of code or configuration causing the break.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Patch the journey&lt;/strong&gt; and verify the fix immediately.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Without the server:&lt;/strong&gt; 45 minutes of context switching (Jira, documentation, console, log aggregators, scripts).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With the AIC MCP Server (and other MCP servers that interact with ticket systems and UX):&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;1. Analyze the Bug and Journey&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Your prompt to Claude:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Read bug report AUTH-802 and show me the current &apos;SSO-Onboarding&apos; journey configuration.  Compare with recent closed tickets that may have caused a breaking change.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The Action:&lt;/strong&gt; Your agent fetches the ticket details using the ticket system MCP server and pulls the journey configuration. It identifies a probable failure that happens during a user lookup step from an external data source.
&lt;strong&gt;Time to insight:&lt;/strong&gt; 30 - 60 seconds.&lt;/p&gt;
&lt;h3&gt;2. Reproduce with Test Users&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Your prompt to Claude:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Create three test users: two with valid metadata in the external data source and one with invalid data to replicate the reported error.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The Action:&lt;/strong&gt; Agent calls &lt;code&gt;createManagedObject&lt;/code&gt; and tools related to the external data source. It returns key details for fresh accounts tailored to your debugging needs.
&lt;strong&gt;Result:&lt;/strong&gt; Environment ready in 20 seconds.&lt;/p&gt;
&lt;h3&gt;3. Trace Logs &amp;amp; Identify the Root Cause&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Your prompt to Claude:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Run a test authentication for the user with invalid data. Show me the logs and highlight any stack traces.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The Action:&lt;/strong&gt; Agent triggers a mock login and calls &lt;code&gt;queryLogs&lt;/code&gt;.
&lt;strong&gt;Result:&lt;/strong&gt; “Found an error relating to user lookup. The journey isn’t handling user lookup where there is invalid data correctly.”
&lt;strong&gt;Time to discovery:&lt;/strong&gt; 5 minutes.&lt;/p&gt;
&lt;h3&gt;4. Update &amp;amp; Re-test&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Your prompt to Claude:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Update the &apos;SSO-Onboarding&apos; journey to include appropriate error handling when user data lookups fail, then re-test with the same user.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The Action:&lt;/strong&gt; Agent calls &lt;code&gt;saveJourney&lt;/code&gt; with the fix and re-runs the authentication trace.
&lt;strong&gt;Result:&lt;/strong&gt; “Test passed. Redirect successful. Journey updated in Dev environment.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Total time saved:&lt;/strong&gt; 30 - 45 minutes of manual work → less than 10 minutes of natural language queries. The workflow can be packaged up into an agent to save more time later. The difference compounds across your team.&lt;/p&gt;
&lt;h2&gt;Security and Caveats&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;OAuth 2.0 Authentication&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every tool call is authenticated with your AIC credentials using OAuth 2.0 (PKCE flow). Your password is never stored. The resulting access token is saved securely in your OS keychain—the same place your browser stores credentials.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Audit Trail&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All actions—queries, creates, updates, deletes—appear in your AIC audit log tied to your user identity and are traceable as MCP-based interactions. There’s full traceability for compliance and security reviews.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sandbox and Development Only&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Currently, the server works with Sandbox and Development AIC tenants only. Production support is on the roadmap. This ensures you can safely experiment and iterate without risk to live systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agent Permissions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Only use the MCP server with AI agents you trust. The agent has the same permissions as your authenticated user. If you’re an AIC admin, the agent is an admin. Always review agent prompts and tool calls before deploying them to production workflows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open Source&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The server is Apache 2.0 licensed and open-source on GitHub. Review the code, audit the dependencies, and contribute. The entire codebase is at &lt;a href=&quot;https://github.com/pingidentity/aic-mcp-server&quot;&gt;github.com/pingidentity/aic-mcp-server&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Next Steps: Try It Today, Give us Feedback&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Run your first agent workflow.&lt;/li&gt;
&lt;li&gt;Consider your developer workflows, how can the MCP server be used to turn repetitive or menial tasks into automated, intelligent workflows?&lt;/li&gt;
&lt;li&gt;Share your use cases and workflows with the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity developer community&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you find issues or would like to see enhancements, &lt;a href=&quot;https://github.com/pingidentity/aic-mcp-server/issues&quot;&gt;raise an issue on the code repository&lt;/a&gt;. We’re listening!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The AIC MCP Server is in early release. Your feedback shapes what comes next!&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Repository: &lt;a href=&quot;https://github.com/pingidentity/aic-mcp-server&quot;&gt;https://github.com/pingidentity/aic-mcp-server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;NPM Package: &lt;a href=&quot;https://www.npmjs.com/package/@ping-identity/aic-mcp-server&quot;&gt;https://www.npmjs.com/package/@ping-identity/aic-mcp-server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PingOne AIC Documentation: &lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/&quot;&gt;https://docs.pingidentity.com/pingoneaic/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Model Context Protocol (MCP) Specification: &lt;a href=&quot;https://modelcontextprotocol.io/specification/2025-11-25&quot;&gt;https://modelcontextprotocol.io/specification/2025-11-25&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OAuth 2.0 PKCE (RFC 7636): &lt;a href=&quot;https://tools.ietf.org/html/rfc7636&quot;&gt;https://tools.ietf.org/html/rfc7636&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.KoQ5fhOb_ZF6ODT.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Introducing the PingOne MCP server</title><link>https://developer.pingidentity.com/blog/introducing-the-pingone-mcp-server/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/introducing-the-pingone-mcp-server/</guid><description>The PingOne MCP Server brings your identity infrastructure into your development workflow. Configure environments, applications, and populations without leaving your IDE.</description><pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Developers building applications using PingOne eventually hit the same friction point. Application code is in the IDE, while application configuration, audit logs, policies, and user journey flows are managed in the web console.&lt;/p&gt;
&lt;p&gt;That context switch is the problem, not the task itself. Registering an application in PingOne or spinning up a new environment for development is straightforward. The cost is the frequent interruption leading to a break in flow, tab switching, and copying and pasting IDs.&lt;/p&gt;
&lt;p&gt;Although frequent switching between IDE and the web console has been well accepted over the years, the expectations of application developers is shifting. Application developers are shifting to AI tools and agentic development practices. Generative code experiences such as VS Code Copilot, Claude Desktop, Claude Code, Gemini CLI, and GPT Codex are changing the ways that developers interact with their application code, and they’re changing the way developers interact with online services.&lt;/p&gt;
&lt;p&gt;The PingOne MCP Server reduces the gap. It connects your PingOne tenant directly to MCP-compatible generative AI IDEs and CLIs. Instead of leaving your editor, you describe what you need from your application integration in natural language, and your agent handles the PingOne API calls, returns the IDs and configuration details you need, and lets you keep iterating on application code.&lt;/p&gt;
&lt;p&gt;This isn’t about replacing the PingOne console for operational work. It’s about making identity platform configuration a first-class citizen of the development cycle, available in the same context where you write code, run tests, and ship features.&lt;/p&gt;
&lt;h2&gt;What Is MCP?&lt;/h2&gt;
&lt;p&gt;System configuration living outside the development cycle isn’t unique to PingOne. But it’s a fundamental challenge in building agentic systems: how do AI agents and IDE AI chat systems connect to the external services they need to act on?&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://modelcontextprotocol.io/docs/getting-started/intro&quot;&gt;Model Context Protocol (MCP)&lt;/a&gt;, originally developed by Anthropic, provides a specification for the interactions between AI systems (the abstract world of “intent”) and today’s systems (the concrete world of deterministic input/output). Before MCP, every integration between an AI agent and an external tool required custom code—bespoke connectors, point-in-time maintenance, duplicated effort across teams. MCP standardizes the connection layer, providing a “USB style” connector interface so that any MCP-compatible client (VS Code Copilot, Claude Desktop, Cursor, and others) can discover and invoke any MCP server’s capabilities using a common protocol, without needing custom code on either side.&lt;/p&gt;
&lt;p&gt;In practical terms, an MCP server currently exposes three types of capabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt; (executable actions the agent can invoke)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resources&lt;/strong&gt; (contextual data to inform reasoning)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompts&lt;/strong&gt; (templated workflows)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The agent discovers tools at runtime, selects what it needs, and calls them through JSON-RPC messages. Prompts and resources are loaded at the user’s request.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can find a deeper dive into MCP’s architecture, workflow, and security model in the &lt;a href=&quot;https://developer.pingidentity.com/identity-for-ai/agents/idai-what-is-mcp.html&quot;&gt;Ping Identity MCP documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;What is the PingOne MCP Server?&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server&quot;&gt;PingOne MCP Server&lt;/a&gt; implements the MCP standard for PingOne’s Management API, exposing 14 tools across 4 capability areas, and represents the start of the journey in producing useful tools to accelerate developer workflows.&lt;/p&gt;
&lt;p&gt;In practical terms: your AI agent in your IDE can now interact with your PingOne tenant the same way it interacts with your filesystem, your terminal, or your version control. It becomes part of the same conversation as your application code.&lt;/p&gt;
&lt;p&gt;The initial set of tools cover what developers need most when building on PingOne:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Applications&lt;/strong&gt;: List, retrieve, create, and update OIDC/OAuth 2.0 applications&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environments&lt;/strong&gt;: List, retrieve, create, and update PingOne environments; manage services (such as MFA and DaVinci)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Populations&lt;/strong&gt;: List, retrieve, create, and update user populations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Directory&lt;/strong&gt;: Generate per-day identity trend reports.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All operations execute as the authenticated user and are logged in PingOne’s audit trail. The agent never sees your access tokens or sign-on credentials; the MCP server handles authentication entirely, returning only the data the agent needs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Missing something you need? Get in touch! Raise an &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server/issues/new&quot;&gt;enhancement request on the code repository&lt;/a&gt;. Your feedback helps drive what we do next.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Getting Started: From Zero to First Tool Call&lt;/h2&gt;
&lt;p&gt;To get started you need a PingOne account (&lt;a href=&quot;https://www.pingidentity.com/en/try-ping.html&quot;&gt;Try Ping here&lt;/a&gt;), an MCP-compatible client (VS Code with Copilot, Cursor, Claude Desktop, or similar).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For the adventurous&lt;/strong&gt;, in your favourite agent IDE, try:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Help me install and configure the PingOne MCP server from github.com/pingidentity/pingone-mcp-server&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Otherwise, follow the instructions below.&lt;/p&gt;
&lt;h3&gt;1. Install the Server&lt;/h3&gt;
&lt;p&gt;The easiest path on macOS or Linux is Homebrew:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; tap&lt;/span&gt;&lt;span&gt; pingidentity/tap&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; pingone-mcp-server&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pingone-mcp-server&lt;/span&gt;&lt;span&gt; --version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;For Windows, the MCP server is built as an executable file, ready to download from the &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server/releases&quot;&gt;project releases&lt;/a&gt;. See the project &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server?tab=readme-ov-file#install-the-mcp-server&quot;&gt;README&lt;/a&gt; for full details for Windows installation and other options.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2. Set Up a Worker Application in PingOne&lt;/h3&gt;
&lt;p&gt;The server needs credentials. Create a worker application in your PingOne tenant:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sign on to the PingOne Administration Console.&lt;/li&gt;
&lt;li&gt;Open the environment for your Administrator users.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Applications&lt;/strong&gt; &amp;gt; &lt;strong&gt;Add (+)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Name it (for example, “PingOne MCP Server”) and choose type &lt;strong&gt;Worker&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Configuration&lt;/strong&gt; and configure OAuth 2.0 settings:
&lt;ul&gt;
&lt;li&gt;Set response type to &lt;strong&gt;Code&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set grant type to &lt;strong&gt;Authorization Code&lt;/strong&gt; (uncheck all others, including &lt;strong&gt;Client Credentials&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;PKCE Enforcement&lt;/strong&gt; to &lt;strong&gt;S256_REQUIRED&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Refresh Token&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set redirect URI to &lt;code&gt;http://127.0.0.1:7464/callback&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set the Token Endpoint Authentication Method to &lt;strong&gt;None&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Save and capture your &lt;strong&gt;Environment ID&lt;/strong&gt; and &lt;strong&gt;Client ID&lt;/strong&gt; from the application details.&lt;/li&gt;
&lt;li&gt;Finally, remember to enable the MCP server application!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&quot;Example application configuration in the PingOne console&quot; loading=&quot;lazy&quot; width=&quot;1708&quot; height=&quot;1338&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/application-configuration.7YgumRgy_xjrxi.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/application-configuration.7YgumRgy_pIUC0.webp 640w, https://developer.pingidentity.com/blog/_astro/application-configuration.7YgumRgy_ZjFoIi.webp 750w, https://developer.pingidentity.com/blog/_astro/application-configuration.7YgumRgy_KIJBc.webp 828w, https://developer.pingidentity.com/blog/_astro/application-configuration.7YgumRgy_OOozt.webp 1080w, https://developer.pingidentity.com/blog/_astro/application-configuration.7YgumRgy_Z2l9Esu.webp 1280w, https://developer.pingidentity.com/blog/_astro/application-configuration.7YgumRgy_2mRdQn.webp 1668w, https://developer.pingidentity.com/blog/_astro/application-configuration.7YgumRgy_xjrxi.webp 1708w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;Although the example here uses the ‘Authorization Code’ grant type, ‘Device Code’ is also available. You can find more details in &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server/blob/main/docs/setup-pingone-worker-application.md&quot;&gt;Setting Up PingOne Worker Applications for MCP Server&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;3. Configure Your MCP Client&lt;/h3&gt;
&lt;p&gt;Once installed, add the server to your MCP client’s configuration. For VS Code with Copilot Chat:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;servers&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;pingOne&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;stdio&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;command&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;pingone-mcp-server&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;args&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;run&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // &quot;--disable-read-only&quot; // uncomment to enable write tools&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;env&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;PINGONE_MCP_ENVIRONMENT_ID&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;your-environment-id-uuid&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;PINGONE_AUTHORIZATION_CODE_CLIENT_ID&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;your-client-id-uuid&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;PINGONE_ROOT_DOMAIN&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;pingone.com&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;If your tenant is in a different region, use &lt;em&gt;pingone.ca&lt;/em&gt;, &lt;em&gt;pingone.eu&lt;/em&gt;, &lt;em&gt;pingone.com.au&lt;/em&gt;, &lt;em&gt;pingone.sg&lt;/em&gt; or &lt;em&gt;pingone.asia&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To install to other IDEs, including Claude Code, Gemini and Cursor, refer to the project &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server&quot;&gt;README&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That’s it. On first tool use, your browser opens your PingOne administrator sign-on page. After you authenticate with your admin credentials, the server caches your token securely in your OS keychain (where available) or is cached ephemerally. Every subsequent tool call validates and reuses the same token.&lt;/p&gt;
&lt;h2&gt;Real-World Use Case: Prepare an Environment for App Development&lt;/h2&gt;
&lt;p&gt;You’re building a new application feature that requires its own isolated identity configuration: a dedicated, ephemeral sandbox environment, a test user population, and an OIDC application for the OAuth flow your code will use.&lt;/p&gt;
&lt;p&gt;Without the MCP server, you stop coding, switch to a browser, walk through the PingOne console to create the environment, toggle the services you need, create the population, register the application, copy the client ID and environment ID back into your project, and resume back.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With the PingOne MCP Server, you stay in your IDE:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that write tools must be enabled using &lt;code&gt;--disable-read-only&lt;/code&gt; at startup.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 1 — Understand what already exists:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;List my PingOne sandbox environments and show me which services are enabled in each.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent calls &lt;code&gt;list_environments&lt;/code&gt; and returns a structured overview. You can see your existing dev and staging environments, what’s running in each, and confirm there’s no overlap with what you’re about to create.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2 — Create the new environment:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;Create a new sandbox environment called &apos;feature-payments-dev&apos;.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Enable the MFA and DaVinci services.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent calls &lt;code&gt;create_environment&lt;/code&gt;, then &lt;code&gt;update_environment_services&lt;/code&gt; to add MFA and DaVinci, and returns the environment ID. You paste it into your &lt;code&gt;.env&lt;/code&gt; file without leaving the editor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3 — Create the test population:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;Create a population called &apos;Payments Test Users&apos; in the&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;feature-payments-dev environment.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent calls &lt;code&gt;create_population&lt;/code&gt;. Returns the population ID. You add it to your test harness configuration inline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4 — Register the OIDC application:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;Create an OIDC application in feature-payments-dev called&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&apos;Payments App&apos;. Use authorization code grant with PKCE enforced.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Redirect URI is http://localhost:3000/callback.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent calls &lt;code&gt;create_oidc_application&lt;/code&gt; with the correct grant type, PKCE settings, and redirect URI. It returns the client ID.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5 — Confirm and move on:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;Give me a summary of the feature-payments-dev environment:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;environment ID, enabled services, population name and ID,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;and the Payments App client ID.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent returns a clean summary, and you’re ready to move on. The entire flow took less than three minutes without switching context once. All four operations are in your PingOne audit log, associated with your user identity.&lt;/p&gt;
&lt;p&gt;The process can be wrapped into a skill or an agent, and the rest of the team can benefit from the workflow.&lt;/p&gt;
&lt;h2&gt;Security Considerations&lt;/h2&gt;
&lt;p&gt;The most important thing to understand about the PingOne MCP Server’s security model: the agent is acting as you. The MCP server doesn’t have its own service account, its own permissions, or its own audit identity. When the agent calls a PingOne API through the MCP server, it calls it as your authenticated user account. Everything it does is bounded by the PingOne roles assigned to your admin account and actions are visible in the audit log, attributable to your identity.&lt;/p&gt;
&lt;p&gt;Other features include:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Production environment restrictions.&lt;/strong&gt; By default, all write actions are disabled on production environments. This is to protect against accidental or unintended configuration changes, even if your user has the ability to make changes to production configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Write operations require an explicit decision when starting the MCP server.&lt;/strong&gt; Running the server without &lt;code&gt;--disable-read-only&lt;/code&gt; exposes only read tools. This is to allow you to explicitly opt in to write tools on your tenant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Full audit trail.&lt;/strong&gt; Every API call from the server includes a session ID and transaction ID in the request headers. You can trace any change back to the MCP server session through PingOne’s audit log.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preview software.&lt;/strong&gt; This is an early release. APIs and behavior may change. Use caution when using the MCP server against important environments. Found a bug? &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server/issues&quot;&gt;Open an issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open source.&lt;/strong&gt; Apache 2.0 licensed. Read the code, audit the dependencies, contribute a fix: &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server&quot;&gt;github.com/pingidentity/pingone-mcp-server&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Next Steps: Try It Today, Give us Feedback&lt;/h2&gt;
&lt;p&gt;The current 14 tools cover a subset of what developers may need when developing on PingOne. But the goal is broader: PingOne configuration should be as accessible from your IDE as your database schema, your API spec, or your CI/CD pipeline. Manual steps you need to take between your code and your identity configuration is a point of friction that can be improved upon.&lt;/p&gt;
&lt;p&gt;Near-term additions include Docker support for containerized workflows and simplified installation, expanded tool coverage across more PingOne APIs, and support for upcoming MCP specification changes as the standard evolves.&lt;/p&gt;
&lt;p&gt;The roadmap is shaped by what you actually need. If there’s a specific tool or capability that would close a gap in your workflow, &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server/issues&quot;&gt;raise an enhancement request&lt;/a&gt; or start a discussion in the repository.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Get a &lt;a href=&quot;https://www.pingidentity.com/en/try-ping.html&quot;&gt;PingOne trial account&lt;/a&gt;&lt;/strong&gt; if you don’t have one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Install and follow the project &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server&quot;&gt;README&lt;/a&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Think about the identity configuration steps in your current developer workflow. Where does context-switching slow you down?&lt;/li&gt;
&lt;li&gt;Share what you build with the &lt;a href=&quot;https://support.pingidentity.com/s/topic/0TO1W000000Q9oRWAS/developer&quot;&gt;Ping Identity community&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your feedback shapes what comes next. Try it today and give us feedback on what you want to build.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PingOne MCP Server code repository: &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server&quot;&gt;https://github.com/pingidentity/pingone-mcp-server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PingOne MCP Server releases: &lt;a href=&quot;https://github.com/pingidentity/pingone-mcp-server/releases&quot;&gt;https://github.com/pingidentity/pingone-mcp-server/releases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PingOne documentation: &lt;a href=&quot;https://docs.pingidentity.com/pingone/latest/&quot;&gt;https://docs.pingidentity.com/pingone/latest/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Model Context Protocol (MCP) specification: &lt;a href=&quot;https://modelcontextprotocol.io/specification/2025-11-25&quot;&gt;https://modelcontextprotocol.io/specification/2025-11-25&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PingOne Management API: &lt;a href=&quot;https://apidocs.pingidentity.com/pingone/platform/v1/api/&quot;&gt;https://apidocs.pingidentity.com/pingone/platform/v1/api/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OAuth 2.0 PKCE (RFC 7636): &lt;a href=&quot;https://tools.ietf.org/html/rfc7636&quot;&gt;https://tools.ietf.org/html/rfc7636&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Homebrew Tap: &lt;a href=&quot;https://github.com/pingidentity/homebrew-tap&quot;&gt;https://github.com/pingidentity/homebrew-tap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.KoQ5fhOb_ZF6ODT.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Rapidly Deploying PingGateway with PingOne Advanced Identity Cloud (P1AIC)</title><link>https://developer.pingidentity.com/blog/rapidly-deploying-pinggateway-with-pingone-advanced-identity-cloud-p1aic/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/rapidly-deploying-pinggateway-with-pingone-advanced-identity-cloud-p1aic/</guid><description>A step-by-step guide to rapidly deploying PingGateway in standalone mode and integrating it with PingOne Advanced Identity Cloud using CDSSO.</description><pubDate>Tue, 03 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This post was originally published on &lt;a href=&quot;https://medium.com/@darinder.shokar/rapidly-deploying-pinggateway-with-pingone-advanced-identity-cloud-p1aic-a500d3734a63&quot;&gt;Medium&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note: This blog applies to PingGateway versions from 2025 onwards. For earlier PingGateway (IG) releases, please refer to the original blog &lt;a href=&quot;https://medium.com/@darinder.shokar/forgerock-identity-gateway-ig-integration-with-the-new-forgerock-identity-cloud-bf87b0b25a08&quot;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Integrating applications securely and efficiently with PingOne Advanced Identity Cloud (P1AIC) is a common requirement for developers and architects exploring modern identity platforms.&lt;/p&gt;
&lt;p&gt;This article demonstrates how to deploy PingGateway in standalone mode and protect a sample application using Cross-Domain Single Sign-On (CDSSO) with P1AIC — all via a single automation script.&lt;/p&gt;
&lt;p&gt;The objective is straightforward:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Enable new users to quickly stand up PingGateway, integrate it with P1AIC, and understand how CDSSO protection works end-to-end.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This deployment is designed for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Learning and enablement&lt;/li&gt;
&lt;li&gt;Proof-of-concept environments&lt;/li&gt;
&lt;li&gt;Developer experimentation&lt;/li&gt;
&lt;li&gt;Workshops and demonstrations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is not intended as production use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The repository containing all assets and the deployment script is located &lt;a href=&quot;https://github.com/forgerock-dss/PingGatewayIntegrationwithP1AIC-2025&quot;&gt;here&lt;/a&gt; on GitHub.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To get straight to deploying go here.&lt;/p&gt;
&lt;h2&gt;Scenario&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;“As a new customer, I want to understand PingGateway through hands-on experimentation — specifically how to configure it to protect an application via CDSSO with P1AIC.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This blog walks through that exact scenario.&lt;/p&gt;
&lt;h2&gt;What the Script Deploys&lt;/h2&gt;
&lt;p&gt;The repository includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;install_ping_gateway_P1AIC.sh&lt;/code&gt; — automation script&lt;/li&gt;
&lt;li&gt;&lt;code&gt;admin.json.HTTPS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;admin.json.HTTP_ONLY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;static-resources.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cdsso-idc.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When executed, the script provisions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;A PingGateway standalone instance&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A Sample Application&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Route configurations that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reverse proxy static resources&lt;/li&gt;
&lt;li&gt;Protects &lt;code&gt;/home/cdsso&lt;/code&gt; via CDSSO with P1AIC&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All components are deployed on the same host for simplicity.&lt;/p&gt;
&lt;h2&gt;Architecture Overview&lt;/h2&gt;
&lt;p&gt;PingGateway runs in &lt;strong&gt;Standalone Mode&lt;/strong&gt;, meaning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It does not require deployment into a servlet container.&lt;/li&gt;
&lt;li&gt;It runs directly from the extracted ZIP.&lt;/li&gt;
&lt;li&gt;It uses Vert.x internally to provide its runtime engine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result is a lightweight, fast-starting gateway instance suitable for development and experimentation.&lt;/p&gt;
&lt;p&gt;The end-to-end flow looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User accesses protected resource via PingGateway.&lt;/li&gt;
&lt;li&gt;PingGateway checks for a valid OIDC token.&lt;/li&gt;
&lt;li&gt;If no valid token is present, the browser is redirected to P1AIC.&lt;/li&gt;
&lt;li&gt;User authenticates with P1AIC.&lt;/li&gt;
&lt;li&gt;P1AIC redirects back to PingGateway.&lt;/li&gt;
&lt;li&gt;PingGateway validates the token and grants access to the sample application.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This demonstrates CDSSO in action, and described in more details below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Cross Domain Single Sign-On (CDSSO) Flow&quot; loading=&quot;lazy&quot; width=&quot;881&quot; height=&quot;942&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/cdsso-flow.CmxaU_xR_1eIIL9.svg&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/cdsso-flow.CmxaU_xR_Zpt14e.svg 640w, https://developer.pingidentity.com/blog/_astro/cdsso-flow.CmxaU_xR_ZNA3mL.svg 750w, https://developer.pingidentity.com/blog/_astro/cdsso-flow.CmxaU_xR_Zhk9Om.svg 828w, https://developer.pingidentity.com/blog/_astro/cdsso-flow.CmxaU_xR_1eIIL9.svg 881w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;To learn more about CDSSO check out &lt;a href=&quot;https://docs.pingidentity.com/pinggateway/latest/gateway-guide/cdsso.html&quot;&gt;this&lt;/a&gt; link&lt;/p&gt;
&lt;h2&gt;Script Execution&lt;/h2&gt;
&lt;h3&gt;Step 1 — Download Assets aond Configure Networking&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Download the ZIP from GitHub &lt;a href=&quot;https://github.com/forgerock-dss/PingGatewayIntegrationwithP1AIC-2025&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Down the PingGateway Standalone ZIP and PingGateway Sample Application JAR from &lt;a href=&quot;https://backstage.forgerock.com/downloads/browse/ig/featured&quot;&gt;here&lt;/a&gt; and place both files in the same directory as the script.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;By default PingGateway will be deployed against this host &lt;code&gt;pinggateway.test.com&lt;/code&gt; and the Sample app to &lt;code&gt;sample.test.com&lt;/code&gt;. Add both to your &lt;code&gt;/etc/hosts&lt;/code&gt; file. For example for IP 172.168.1.10 the hosts entry will look like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;172.168.1.10 pinggateway.test.com sample.test.com&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Step 2 — Configure the Script&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Modify lines 11–30 in the &lt;code&gt;install_ping_gateway_P1AIC.sh&lt;/code&gt; script to match your environment&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Step 3 — Configure P1AIC&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Create a Test User&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Log in to P1AIC.&lt;/li&gt;
&lt;li&gt;Select the appropriate realm.&lt;/li&gt;
&lt;li&gt;Navigate to Identities → Manage.&lt;/li&gt;
&lt;li&gt;Create a new user.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Create a Gateway Agent&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Navigate to Gateways and Agents.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click New Gateway/Agent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select Identity Gateway.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter the following and hit Save:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Agent ID (e.g. &lt;code&gt;pinggateway_agent_cdsso&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Password&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ensure these values match the script configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AGENT_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AGENT_SECRET&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Configure Redirect URIs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HTTPS:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://pinggateway.test.com:9443/home/cdsso/redirect&quot;&gt;https://pinggateway.test.com:9443/home/cdsso/redirect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;HTTP:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://pinggateway.test.com:9000/home/cdsso/redirect&quot;&gt;http://pinggateway.test.com:9000/home/cdsso/redirect&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Step 4 — Execute the Script&lt;/h3&gt;
&lt;p&gt;For P1AIC deployments, use HTTPS mode due to SameSite cookie requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run: &lt;code&gt;./install_ping_gateway_P1AIC.sh https&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;For standalone PingAM deployments run &lt;code&gt;./install_ping_gateway_P1AIC.sh http&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Step 5 — Validate Deployment&lt;/h3&gt;
&lt;p&gt;After deployment, access:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://pinggateway.test.com:9443/home/cdsso&quot;&gt;https://pinggateway.test.com:9443/home/cdsso&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You should be redirected to P1AIC for authentication. Upon successful login, the Sample Application page will be displayed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note: If you were already logged into P1AIC during setup, log out first or use a private browser window to prevent cookie conflicts.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Sample CDSSO Protected Application&quot; loading=&quot;lazy&quot; width=&quot;850&quot; height=&quot;746&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/sample-cdsso-protected-application.BrPjiuwz_ZrlDqV.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/sample-cdsso-protected-application.BrPjiuwz_23Ce13.webp 640w, https://developer.pingidentity.com/blog/_astro/sample-cdsso-protected-application.BrPjiuwz_1H8Uxs.webp 750w, https://developer.pingidentity.com/blog/_astro/sample-cdsso-protected-application.BrPjiuwz_1QcvvP.webp 828w, https://developer.pingidentity.com/blog/_astro/sample-cdsso-protected-application.BrPjiuwz_ZrlDqV.webp 850w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h3&gt;Step 6— Stop/Start the PingGateway&lt;/h3&gt;
&lt;p&gt;To stop/start PingGateway and the Sample App use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stop Services: &lt;code&gt;./install_ping_gateway_P1AIC.sh stop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Start Services: &lt;code&gt;./install_ping_gateway_P1AIC.sh start&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Script Breakdown&lt;/h2&gt;
&lt;p&gt;The script performs the following high-level steps.&lt;/p&gt;
&lt;h3&gt;1. Environment Validation&lt;/h3&gt;
&lt;p&gt;The script validates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PingGateway ZIP is present&lt;/li&gt;
&lt;li&gt;Sample Application JAR is present&lt;/li&gt;
&lt;li&gt;Required commands are available (&lt;code&gt;unzip&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;The host can connect to the configured P1AIC endpoint&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Execution stops if validation fails.&lt;/p&gt;
&lt;h3&gt;2. Clean Installation Handling&lt;/h3&gt;
&lt;p&gt;If a previous installation exists:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PingGateway is stopped&lt;/li&gt;
&lt;li&gt;The sample application is stopped&lt;/li&gt;
&lt;li&gt;The installation directory is removed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This ensures clean redeployment.&lt;/p&gt;
&lt;h3&gt;3. PingGateway Deployment&lt;/h3&gt;
&lt;p&gt;The script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extracts the PingGateway ZIP&lt;/li&gt;
&lt;li&gt;Performs a temporary start/stop cycle to initialise directories&lt;/li&gt;
&lt;li&gt;Creates required configuration folders&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PingGateway is deployed in Production Mode by default.&lt;/p&gt;
&lt;h3&gt;4. Script Execution Mode&lt;/h3&gt;
&lt;p&gt;The script supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https mode (recommended for P1AIC)&lt;/li&gt;
&lt;li&gt;http mode (for standalone PingAM deployments)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When HTTPS mode is selected, a self-signed certificate is generated automatically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note — Self-signed certificates are for demonstration only and should not be used in production.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;5. Sample Application Deployment&lt;/h3&gt;
&lt;p&gt;The sample application is deployed locally and started on the configured ports, providing a target application for PingGateway to protect.&lt;/p&gt;
&lt;h3&gt;6. Route Configuration&lt;/h3&gt;
&lt;p&gt;Two routes are created:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;static-resources.json&lt;/code&gt; — Allows PingGateway to proxy static resources (e.g., CSS) without authentication. This improves performance and mirrors real-world deployment patterns.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cdsso-idc.json&lt;/code&gt; — Protects the /home/cdsso context. It checks for a valid OIDC id_token, redirects unauthenticated users to P1AIC, validates tokens on return and grants access upon successful validation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This script provides a fast, repeatable way to deploy PingGateway locally and integrate it with PingOne Advanced Identity Cloud using CDSSO — all within minutes. It enables you to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deploy PingGateway in standalone mode&lt;/li&gt;
&lt;li&gt;Understand how standalone runtime operates&lt;/li&gt;
&lt;li&gt;Explore CDSSO behaviour with P1AIC&lt;/li&gt;
&lt;li&gt;Observe complete end-to-end authentication flows&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By simplifying setup and automating the foundational configuration, this approach accelerates learning and removes friction from experimentation. It serves as an ideal starting point for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developers building familiarity with PingGateway&lt;/li&gt;
&lt;li&gt;Architects validating integration patterns&lt;/li&gt;
&lt;li&gt;Technical enablement sessions and workshops&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most importantly, it provides a clear, structured baseline from which you can extend into more advanced routing logic, security hardening, certificate management, and production-ready deployment patterns.&lt;/p&gt;
&lt;p&gt;In short, this script turns what could be a multi-step manual configuration process into a streamlined, educational experience — helping you move from exploration to implementation with confidence.&lt;/p&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;CDSSO Documentation — &lt;a href=&quot;https://docs.pingidentity.com/pinggateway/latest/aic/cdsso.html&quot;&gt;https://docs.pingidentity.com/pinggateway/latest/aic/cdsso.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PingGateway Documentation — &lt;a href=&quot;https://docs.pingidentity.com/pinggateway/latest/&quot;&gt;https://docs.pingidentity.com/pinggateway/latest/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.CLIR6Db2_ungpY.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Securing High-Risk Actions: Transactional Authorization over REST</title><link>https://developer.pingidentity.com/blog/securing-high-risk-actions-transactional-authorization-over-rest/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/securing-high-risk-actions-transactional-authorization-over-rest/</guid><description>A practical guide to securing high-risk actions with REST-based Transactional Authorization.</description><pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This post was originally published on &lt;a href=&quot;https://medium.com/@darinder.shokar/securing-high-risk-actions-transactional-authorization-over-rest-8e3a6fbf474b&quot;&gt;Medium&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Requiring a user to explicitly authorise access to a sensitive resource every time they attempt to access it is a common security requirement. A typical example is in financial services, where initiating a payment might trigger a one-time passcode (OTP) sent to the user’s device, or a push notification requiring approval. These additional steps increase friction intentionally, ensuring the user is genuinely present and consenting before a high-risk action is allowed.&lt;/p&gt;
&lt;p&gt;In the PingOne Advanced Identity Cloud (P1AIC) and PingOne Advanced Identity Software (self-managed PingAM), this capability is known as Transactional Authorisation (TAuthZ). You can read more about it &lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/am-authorization/transactional-authorization.html#understand_transactional_authorization&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Transactional Authorization in P1AIC and PingAM&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/transactional-authorization.CraZbp4r_Z1nvUyK.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/transactional-authorization.CraZbp4r_Zb819K.webp 640w, https://developer.pingidentity.com/blog/_astro/transactional-authorization.CraZbp4r_ZtAO2y.webp 750w, https://developer.pingidentity.com/blog/_astro/transactional-authorization.CraZbp4r_28ri3T.webp 828w, https://developer.pingidentity.com/blog/_astro/transactional-authorization.CraZbp4r_Z1nvUyK.webp 1024w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;The most common and recommended architecture for enabling Transactional Authorisation is to use a Policy Enforcement Point (PEP) such as &lt;a href=&quot;https://docs.pingidentity.com/pinggateway/2025.9/gateway-guide/stepup-sso-trx.html&quot;&gt;PingGateway&lt;/a&gt; or the &lt;a href=&quot;https://docs.pingidentity.com/web-agents/2025.9/index.html&quot;&gt;PingAgent&lt;/a&gt;, which interact with P1AIC or PingAM as the Policy Decision Point (PDP). Another pattern is to implement enforcement directly in applications via the &lt;a href=&quot;https://docs.pingidentity.com/sdks/latest/sdks/use-cases/how-to-perform-transactional-authorization.html&quot;&gt;Ping SDKs&lt;/a&gt; (JavaScript, iOS and Android).&lt;/p&gt;
&lt;p&gt;However, some customers who operate their own application gateways prefer to interact directly with the PDP REST APIs. This blog focuses on that third approach and provides a clear, end-to-end example using an automated shell script to demonstrate how the REST flow works.&lt;/p&gt;
&lt;p&gt;The high-level sequence of events for both application-based and gateway-based integrations is broadly the same. It’s explained in detail &lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/am-authorization/transactional-authorization.html#understand_transactional_authorization&quot;&gt;here&lt;/a&gt;, but here’s the simplified flow:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Application and Gateway Flow for Transactional Authorization&quot; loading=&quot;lazy&quot; width=&quot;1122&quot; height=&quot;917&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/application-gateway-flow.BeX41Iyh_Z1CmUwe.svg&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/application-gateway-flow.BeX41Iyh_1oK9XR.svg 640w, https://developer.pingidentity.com/blog/_astro/application-gateway-flow.BeX41Iyh_1LWkU4.svg 750w, https://developer.pingidentity.com/blog/_astro/application-gateway-flow.BeX41Iyh_23fq5V.svg 828w, https://developer.pingidentity.com/blog/_astro/application-gateway-flow.BeX41Iyh_2wVXuQ.svg 1080w, https://developer.pingidentity.com/blog/_astro/application-gateway-flow.BeX41Iyh_Z1CmUwe.svg 1122w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;Use Case&lt;/h2&gt;
&lt;p&gt;In this blog, we demonstrate a REST-based approach to Transactional Authorization (TAuthZ) using an automated shell script. The scenario covers a high-risk operation — accessing an example &lt;code&gt;makePayment&lt;/code&gt; API&lt;br /&gt;
(&lt;code&gt;https://api.bankingexample.com:443/makepayment&lt;/code&gt;) via an &lt;code&gt;HTTP POST&lt;/code&gt; request.&lt;/p&gt;
&lt;p&gt;The script performs the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authenticate the user using a standard login journey and obtain an SSO token&lt;/li&gt;
&lt;li&gt;Authenticate an admin using an admin-specific login journey in order to access the policy endpoint, issuing an SSO token with extended session durations (for example, a 60-minute idle and max time) so the client can cache it and improve performance.&lt;/li&gt;
&lt;li&gt;Calls the policy evaluation endpoint to request access to the protected &lt;code&gt;makePayment&lt;/code&gt; API.&lt;/li&gt;
&lt;li&gt;Receive a policy advice with a &lt;code&gt;TransactionConditionAdvice&lt;/code&gt; (TxId) from the PDP instructing the client to execute a Transactional Authorization journey (for example, OTP or push approval).&lt;/li&gt;
&lt;li&gt;Executes the required TAuthZ journey with this &lt;code&gt;TxId&lt;/code&gt;, collecting and completing the necessary callbacks for the one-time step-up authentication flow.&lt;/li&gt;
&lt;li&gt;Resubmits the policy evaluation request, now including the completed transactional context and &lt;code&gt;TxId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally, receives a one-time authorization decision granting access to the sensitive API.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This demonstrates how applications or gateways can interact directly with the PDP REST APIs to enforce secure, single-use authorization for high-risk operations.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;Let’s get building 💪&lt;/p&gt;
&lt;p&gt;All assets are located &lt;a href=&quot;https://github.com/forgerock-dss/transactional_authorization/tree/main&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Import the Journeys&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For this example we’ll import three journeys:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NormalLogin&lt;/code&gt; — Used to simulate a standard user login.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AdminLogin&lt;/code&gt; — Used to simulate admin login with extended session timeouts.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TransactionalAuthZLogin&lt;/code&gt; — Used as the step-up authentication journey triggered by the TAuthZ policy.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Download the journey export from &lt;a href=&quot;https://github.com/forgerock-dss/transactional_authorization/blob/main/journey_exports/normal_admin_and_transactional_journey_export.json&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;From the P1AIC Platform UI, navigate to Journeys on the left panel &amp;gt; Click Import &amp;gt; Take a Backup if required &amp;gt; Select Browse &amp;gt; Locate and open the file from step 1 &amp;gt; Click Next &amp;gt; Start Import&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once the import is complete, the three new journeys will appear as shown below. Note that the &lt;code&gt;TransactionalAuthZLogin&lt;/code&gt; journey should be a strong MFA journey. However, a simplified version has been used here for clarity in this blog.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;AdminLogin with Extended Session Timeouts&quot; loading=&quot;lazy&quot; width=&quot;1400&quot; height=&quot;472&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/adminlogin-journey.CkVHw3Bb_2gDbOr.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/adminlogin-journey.CkVHw3Bb_1l0dMf.webp 640w, https://developer.pingidentity.com/blog/_astro/adminlogin-journey.CkVHw3Bb_1fh1yr.webp 750w, https://developer.pingidentity.com/blog/_astro/adminlogin-journey.CkVHw3Bb_zkVFO.webp 828w, https://developer.pingidentity.com/blog/_astro/adminlogin-journey.CkVHw3Bb_1o4wu8.webp 1080w, https://developer.pingidentity.com/blog/_astro/adminlogin-journey.CkVHw3Bb_Z200P8c.webp 1280w, https://developer.pingidentity.com/blog/_astro/adminlogin-journey.CkVHw3Bb_2gDbOr.webp 1400w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Normal User Login Journey&quot; loading=&quot;lazy&quot; width=&quot;1400&quot; height=&quot;579&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/normallogin-journey.By6QKLeC_ZjppcX.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/normallogin-journey.By6QKLeC_Z9pjjD.webp 640w, https://developer.pingidentity.com/blog/_astro/normallogin-journey.By6QKLeC_Z1wXcxP.webp 750w, https://developer.pingidentity.com/blog/_astro/normallogin-journey.By6QKLeC_ZiVXSz.webp 828w, https://developer.pingidentity.com/blog/_astro/normallogin-journey.By6QKLeC_1Nc8mI.webp 1080w, https://developer.pingidentity.com/blog/_astro/normallogin-journey.By6QKLeC_Z1rA6wr.webp 1280w, https://developer.pingidentity.com/blog/_astro/normallogin-journey.By6QKLeC_ZjppcX.webp 1400w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Step Up Journey Targeted by the TAuthZ Policy&quot; loading=&quot;lazy&quot; width=&quot;1251&quot; height=&quot;615&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/transactionalauthzlogin-journey.ChTMQ0O-_ZsKRSx.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/transactionalauthzlogin-journey.ChTMQ0O-_Z642hp.webp 640w, https://developer.pingidentity.com/blog/_astro/transactionalauthzlogin-journey.ChTMQ0O-_1OAvl4.webp 750w, https://developer.pingidentity.com/blog/_astro/transactionalauthzlogin-journey.ChTMQ0O-_1HkNHO.webp 828w, https://developer.pingidentity.com/blog/_astro/transactionalauthzlogin-journey.ChTMQ0O-_1DfYqo.webp 1080w, https://developer.pingidentity.com/blog/_astro/transactionalauthzlogin-journey.ChTMQ0O-_ZsKRSx.webp 1251w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Import the Transactional Authorization Policy&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download the policy XML file from &lt;a href=&quot;https://github.com/forgerock-dss/transactional_authorization/blob/main/policy_exports/Transactional_AuthZ_Policy_Export.xml&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;From the P1AIC Platform UI, expand Native Consoles &amp;gt; Click Access Management &amp;gt; Expand Authorization &amp;gt; Click Policy Sets.&lt;/li&gt;
&lt;li&gt;Click Import Policy Sets &amp;gt; Find the XML file from step 1 &amp;gt; Click Open&lt;/li&gt;
&lt;li&gt;Refresh the browser and the new &lt;code&gt;transactionalAuthZ&lt;/code&gt; Policy Set will appear.&lt;/li&gt;
&lt;li&gt;Clicking into it will show the &lt;code&gt;makePayment&lt;/code&gt; policy as shown below where:
&lt;ul&gt;
&lt;li&gt;The resource is defined as &lt;code&gt;https://api.bankingexample.com:443/makepayment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The allowed action is &lt;code&gt;HTTP POST.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The subject is set to &lt;code&gt;AuthenticatedUsers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The Transaction Environments condition targets the &lt;code&gt;TransactionalAuthZLogin&lt;/code&gt; journey.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&quot;makePayment Transactional Authorization Policy&quot; loading=&quot;lazy&quot; width=&quot;1335&quot; height=&quot;695&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/makepayment-policy.CzCg7T4K_Z1JgjSd.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/makepayment-policy.CzCg7T4K_GTRla.webp 640w, https://developer.pingidentity.com/blog/_astro/makepayment-policy.CzCg7T4K_Z2dvzFi.webp 750w, https://developer.pingidentity.com/blog/_astro/makepayment-policy.CzCg7T4K_1FKBQj.webp 828w, https://developer.pingidentity.com/blog/_astro/makepayment-policy.CzCg7T4K_ZTcbh9.webp 1080w, https://developer.pingidentity.com/blog/_astro/makepayment-policy.CzCg7T4K_theVc.webp 1280w, https://developer.pingidentity.com/blog/_astro/makepayment-policy.CzCg7T4K_Z1JgjSd.webp 1335w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create the Test Users and Apply Privileges&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;From the P1AIC Platform UI, Expand Identities &amp;gt; Click Manage &amp;gt; New Alpha realm — user &amp;gt; Create a user with the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Username: policy_user&lt;/li&gt;
&lt;li&gt;Firstname: Policy&lt;/li&gt;
&lt;li&gt;Lastname: User&lt;/li&gt;
&lt;li&gt;Email Address: &lt;a href=&quot;mailto:policy_user@example.com&quot;&gt;policy_user@example.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Password: &lt;code&gt;!!_SeCu4E@Pa55w04D$&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Repeat Step 1 but this time for the policy admin user:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Username: policy_admin&lt;/li&gt;
&lt;li&gt;Firstname: Policy&lt;/li&gt;
&lt;li&gt;Lastname: Admin&lt;/li&gt;
&lt;li&gt;Email Address: &lt;a href=&quot;mailto:policy_admin@example.com&quot;&gt;policy_admin@example.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Password: &lt;code&gt;!!_Adm1nSeCu4E@Pa55w04D$&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;From the Access Management Native Console, navigate to Identities &amp;gt; Groups tab &amp;gt; Add Group &amp;gt; Set the Group ID to &lt;code&gt;policy_admin&lt;/code&gt; &amp;gt; Create.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the &lt;code&gt;policy_admin&lt;/code&gt; user as a member and hit Save.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;From the Privileges tab, enable &lt;code&gt;Entitlement Rest Access&lt;/code&gt; and hit Save Changes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Execute the Script&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now that we have all the configuration in place, let’s bring it to life with the script.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download the script from &lt;a href=&quot;https://github.com/forgerock-dss/transactional_authorization/blob/main/scripts/transactional_authZ.sh&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Modify the AM_URL to match your environment&lt;/li&gt;
&lt;li&gt;Execute the script to demonstrate the flow&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The output will look this like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;./transactional_authZ_DS.sh  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Getting cookie name  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;CookieName is: XXXX&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*********************  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Creating end-user SSO token for user: policy_user  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;End-user SSO token is: ZER7HY8OL...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*********************  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Creating policy-admin SSO token for user: policy_admin  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Policy-admin SSO Token is: o0jzAU....&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*********************  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Calling transactional policy: transactionalAuthZ  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Transaction Condition Advice Id is: 195b8505-3dba-4bbc-a71a-7debed2c2596&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*********************  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Calling ../authenticate endpoint with Transaction Condition Advice Id: 195b8505-3dba-4bbc-a71a-7debed2c2596 to get callbacks&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*********************  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Completing callbacks for submission  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Completed callback payload:  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;authId&quot;: &quot;eyJ0eXAiOiJKV1QiLC....74&quot;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;callbacks&quot;: [  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;....&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*********************  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Calling authenticate endpoint with advice and completed callbacks  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;tokenId&quot;: &quot;ZER7HY8O....IwMQ..*&quot;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;successUrl&quot;: &quot;/enduser/?realm=/alpha&quot;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;realm&quot;: &quot;/alpha&quot;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*********************  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Finally, calling policy endpoint post transactional AuthZ with Transactional Conditional Advice Id of: 195b8505-3dba-4bbc-a71a-7debed2c2596  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;resource&quot;: &quot;https://api.bankingexample.com:443/makepayment&quot;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;actions&quot;: {  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;POST&quot;: true  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;},  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;attributes&quot;: {},  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;advices&quot;: {},  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;ttl&quot;: 0  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*********************&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Transactional Authorization (TAuthZ) is a powerful capability within PingOne Advanced Identity Cloud (P1AIC) and PingAM, enabling organisations to enforce strong, step-up authentication precisely at the moments where risk is highest. Whether approving a financial transaction, confirming a high-value action, or validating a user’s intent, TAuthZ offers a reliable way to introduce friction only when it truly matters.&lt;/p&gt;
&lt;p&gt;This blog illustrates how to execute a complete Transactional Authorization flow entirely over REST. By walking through user authentication, admin token retrieval, policy evaluation, TxId handling, step-up journey execution, and final policy re-evaluation, we showed how an application or custom gateway can interact directly with the PDP — while still leveraging the full power and flexibility of PingAM’s decisioning capabilities.&lt;/p&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.DMaHsK8J_Z7WL5D.webp" medium="image" type="image/webp" width="1480" height="740"/></item><item><title>Smarter “Remember Me” with PingOne Protect</title><link>https://developer.pingidentity.com/blog/smarter-remember-me-with-pingone-protect/</link><guid isPermaLink="true">https://developer.pingidentity.com/blog/smarter-remember-me-with-pingone-protect/</guid><description>Smarter “Remember Me” with PingOne Protect: seamless, secure logins that adapt to risk without disrupting the user experience.</description><pubDate>Tue, 21 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This post was originally published on &lt;a href=&quot;https://medium.com/@darinder.shokar/smarter-remember-me-with-pingone-protect-f71a54d7fd80&quot;&gt;Medium&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In the world of Identity and Access Management (IAM), it’s always a balancing act between security and user experience. Organisations want to introduce just the right amount of friction, at the right moment, and in the right context to protect users without disrupting them. The goal is to maintain a seamless, trusted customer journey while minimising drop-off and frustration.&lt;/p&gt;
&lt;p&gt;To achieve this, many businesses have implemented a “Remember Me” capability within their web channels. Once a user successfully signs in, subsequent visits allow them to be logged in automatically without re-entering their credentials — a familiar and convenient feature which is especially common in retail environments.&lt;/p&gt;
&lt;p&gt;However, “Remember Me” in isolation has its flaws. While it delivers convenience, it often lacks the intelligence to adapt when the context changes. For example, what if a user originally authenticates from the UK but suddenly appears to log in from Singapore — an &lt;em&gt;impossible traveller&lt;/em&gt; scenario? What if their IP address shifts unexpectedly, the login occurs at an unusual time, or even worse, a bot attempts access? Without continuously evaluating these contextual signals, “Remember Me” might meet the bar for user experience but falls short on adaptive security — failing to introduce the right friction at the right moment.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Strengthening Remember Me with PingOne Protect&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/remember-me.DSUkWQZe_9FSHD.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/remember-me.DSUkWQZe_Z1AJsMy.webp 640w, https://developer.pingidentity.com/blog/_astro/remember-me.DSUkWQZe_Z1XrSul.webp 750w, https://developer.pingidentity.com/blog/_astro/remember-me.DSUkWQZe_Z2cfNHv.webp 828w, https://developer.pingidentity.com/blog/_astro/remember-me.DSUkWQZe_9FSHD.webp 1024w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;This is where &lt;a href=&quot;https://www.pingidentity.com/en/product/pingone-protect.html&quot;&gt;PingOne Protect&lt;/a&gt; steps in. It continuously analyses behavioural and contextual signals across multiple attack vectors to assign risk scores and trigger appropriate mitigation or action, to ensure convenience is not achieved at the expense of security.&lt;/p&gt;
&lt;p&gt;To learn more about the use cases for PingOne Protect integration with PingOne Advanced Identity Cloud (P1AIC) check out &lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/integrations/pingone-protect-use-cases.html&quot;&gt;this&lt;/a&gt; link.&lt;/p&gt;
&lt;h2&gt;Use Case&lt;/h2&gt;
&lt;p&gt;In this blog, we’ll enhance the &lt;a href=&quot;https://docs.pingidentity.com/auth-node-ref/latest/persistent-cookie-decision.html&quot;&gt;Persistent Cookie&lt;/a&gt; feature in PingOne Advanced Identity Cloud — the functionality that enables the “Remember Me” capability by integrating it with PingOne Protect.&lt;/p&gt;
&lt;p&gt;Here’s how it works:&lt;/p&gt;
&lt;p&gt;During the initial login, PingOne Protect evaluates the context and returns a risk score:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the score is low, a Persistent Cookie (PCookie) is set, and the login proceeds as normal.&lt;/li&gt;
&lt;li&gt;If the score is not low, no PCookie is set — effectively disabling “Remember Me” for that session, but does so silently, without disrupting the user experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On subsequent visits, if a valid PCookie is found, PingOne Protect performs another risk assessment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the risk remains low, the cookie is trusted, a new session is created automatically and the user is seamlessly signed in.&lt;/li&gt;
&lt;li&gt;If the risk is elevated, the user isn’t blocked outright; instead, the PCookie is deleted and they’re redirected to re-enter their credentials — introducing just the right level of friction, at the right moment, based on the current context and signals.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;Let’s get building! 💪&lt;/p&gt;
&lt;p&gt;Before we dive in, it’s worth noting that this solution showcases the power of the PingOne platform — apart from a single Custom Node used to display nodeState variables for clarity and education, it’s built entirely with out-of-the-box nodes, requiring no customisation or extensions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup a Mapped PingOne Environment&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this section we’ll create a PingOne environment with PingOne Protect deployed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If you have a PingOne subscription navigate to &lt;a href=&quot;https://www.pingidentity.com/&quot;&gt;this&lt;/a&gt; page and hit “Sign On” at the top right of the page and login. On success you’ll be re-directed to the &lt;a href=&quot;https://console.pingone.eu/&quot;&gt;https://console.pingone.eu/&lt;/a&gt; page. If you don’t have a subscription, you can get a demo environment through this &lt;a href=&quot;https://www.pingidentity.com/en/try-ping.html&quot;&gt;link&lt;/a&gt;. Enter your business email address and hit “Try Ping”.&lt;/li&gt;
&lt;li&gt;From the PingOne Console &amp;gt; Hit Environments on the left panel &amp;gt; Blue + icon next to Environments.&lt;/li&gt;
&lt;li&gt;Select Build your own solution &amp;gt; click PingOne Protect &amp;gt; Hit Next.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&quot;PingOne Environment Creation Wizard&quot; loading=&quot;lazy&quot; width=&quot;1962&quot; height=&quot;853&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/create-environment.NqlHx8GV_Z2bV2vn.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/create-environment.NqlHx8GV_694B7.webp 640w, https://developer.pingidentity.com/blog/_astro/create-environment.NqlHx8GV_Z16Y80C.webp 750w, https://developer.pingidentity.com/blog/_astro/create-environment.NqlHx8GV_Z1OVqGz.webp 828w, https://developer.pingidentity.com/blog/_astro/create-environment.NqlHx8GV_1Y6Rvn.webp 1080w, https://developer.pingidentity.com/blog/_astro/create-environment.NqlHx8GV_52JYn.webp 1280w, https://developer.pingidentity.com/blog/_astro/create-environment.NqlHx8GV_Z1cpQM5.webp 1668w, https://developer.pingidentity.com/blog/_astro/create-environment.NqlHx8GV_Z2bV2vn.webp 1962w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;On the environment name enter an appropriate name, for example &lt;code&gt;env-pingoneaic-**mycompany-ew2-sandbox1**&lt;/code&gt; &amp;gt; Select the region &amp;gt; Hit Finish.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Create a Worker Application&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Follow &lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/integrations/pingone-set-up-workers.html#create-a-worker-application-in-each-mapped-pingone-environment&quot;&gt;these&lt;/a&gt; steps to create OIDC credentials for the PingOne AIC tenant to integrate with PingOne Protect. Note the PingOne API and Authorization URLs, for example &lt;a href=&quot;https://auth.pingone.eu/&quot;&gt;https://auth.pingone.&lt;/a&gt;com, &lt;a href=&quot;https://auth.pingone.eu/&quot;&gt;https://auth.pingone.eu&lt;/a&gt; etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create Environment Secrets and Variables (ESVs) in P1AIC&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Follow &lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/integrations/pingone-set-up-workers.html#create-esvs-for-the-worker-application-credentials-in-each-tenant-environment&quot;&gt;these&lt;/a&gt; steps to create three ESVs that map to the PingOne Worker Credentials from the last step.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Additionally, create an ESV named &lt;code&gt;esv-hmac-signing-key&lt;/code&gt; (string secret) to sign the persistent cookie. Generate a 256-bit key using one of the following commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OpenSSL: &lt;code&gt;openssl rand -base64 32&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally create an ESV (string variable) called &lt;code&gt;esv-persist-cookie-domain&lt;/code&gt;with a value of the Fully Qualified Domain Name of your tenant. For example &lt;code&gt;openam-darinder-test.forgerock.io&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These ESVs make it easy to update configuration values when promoting to higher environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create a PingOne Service in P1AIC&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Follow &lt;a href=&quot;https://docs.pingidentity.com/pingoneaic/latest/integrations/pingone-set-up-workers.html#create-a-service-in-your-development-environment&quot;&gt;these&lt;/a&gt; steps to create a PingOne Service in your P1AIC tenant. For the secondary configuration be sure to use the ESVs defined above.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Secondary Configuration with ESVs&quot; loading=&quot;lazy&quot; width=&quot;1584&quot; height=&quot;564&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/secondary-configuration.BT9dlTuH_Z1U7VXT.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/secondary-configuration.BT9dlTuH_Z1duDyJ.webp 640w, https://developer.pingidentity.com/blog/_astro/secondary-configuration.BT9dlTuH_Z2sCAAl.webp 750w, https://developer.pingidentity.com/blog/_astro/secondary-configuration.BT9dlTuH_Z2rylc8.webp 828w, https://developer.pingidentity.com/blog/_astro/secondary-configuration.BT9dlTuH_Z2idnWd.webp 1080w, https://developer.pingidentity.com/blog/_astro/secondary-configuration.BT9dlTuH_e1ugr.webp 1280w, https://developer.pingidentity.com/blog/_astro/secondary-configuration.BT9dlTuH_Z1U7VXT.webp 1584w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;After creating the configuration set the PingOne API Server URL and Authorization Server URL as per the address noted in the “Create a Worker Application” section:
&lt;img alt=&quot;PingOne API and Authorization Server URL has been set to .eu&quot; loading=&quot;lazy&quot; width=&quot;1607&quot; height=&quot;545&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/pingoneworkerservice.DJEICWdi_Z2gROds.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/pingoneworkerservice.DJEICWdi_2dSOLe.webp 640w, https://developer.pingidentity.com/blog/_astro/pingoneworkerservice.DJEICWdi_ZqMSj8.webp 750w, https://developer.pingidentity.com/blog/_astro/pingoneworkerservice.DJEICWdi_Z1AzIh0.webp 828w, https://developer.pingidentity.com/blog/_astro/pingoneworkerservice.DJEICWdi_Z28meN1.webp 1080w, https://developer.pingidentity.com/blog/_astro/pingoneworkerservice.DJEICWdi_Z15Fbce.webp 1280w, https://developer.pingidentity.com/blog/_astro/pingoneworkerservice.DJEICWdi_Z2gROds.webp 1607w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Import the Custom Display NodeState Node&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download &lt;code&gt;Display-NodeState.json&lt;/code&gt; from GitHub &lt;a href=&quot;https://github.com/forgerock-dss/remember_me_pingone_protect/tree/main/custom_nodes&quot;&gt;here&lt;/a&gt; to your local machine.&lt;/li&gt;
&lt;li&gt;From the P1AIC platform admin UI, expand Journeys on the left navigation panel &amp;gt; Custom Nodes&lt;/li&gt;
&lt;li&gt;Click Import Nodes (or Import if other Custom Nodes are present) &amp;gt; Browse &amp;gt; open &lt;code&gt;Display-NodeState.json&lt;/code&gt; &amp;gt; Import Nodes &amp;gt; Done. A new node called Output Variable from NodeState should be created.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Journey Import&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;From the &lt;code&gt;journey_exports&lt;/code&gt; directory &lt;a href=&quot;https://github.com/forgerock-dss/remember_me_pingone_protect/tree/main/journey_exports&quot;&gt;here&lt;/a&gt;, download the journey to your local machine.&lt;/li&gt;
&lt;li&gt;From the P1AIC platform admin UI, expand Journeys on the left navigation panel &amp;gt; click Journeys &amp;gt; Import.&lt;/li&gt;
&lt;li&gt;If need be take a backup or skip.&lt;/li&gt;
&lt;li&gt;Click Browse &amp;gt; find the &lt;code&gt;PersistWithProtect-journeyExport.json&lt;/code&gt; file &amp;gt; Open &amp;gt; Next &amp;gt; Start Import.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&quot;Persistent Cookie Integrated with PingOne Protect&quot; loading=&quot;lazy&quot; width=&quot;2001&quot; height=&quot;1068&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/persistent-cookie.C_TG_vEa_Z1X9IoL.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/persistent-cookie.C_TG_vEa_Z1Lq667.webp 640w, https://developer.pingidentity.com/blog/_astro/persistent-cookie.C_TG_vEa_1W8yNs.webp 750w, https://developer.pingidentity.com/blog/_astro/persistent-cookie.C_TG_vEa_11Y7GF.webp 828w, https://developer.pingidentity.com/blog/_astro/persistent-cookie.C_TG_vEa_jUpEV.webp 1080w, https://developer.pingidentity.com/blog/_astro/persistent-cookie.C_TG_vEa_1XLikp.webp 1280w, https://developer.pingidentity.com/blog/_astro/persistent-cookie.C_TG_vEa_Z12Csef.webp 1668w, https://developer.pingidentity.com/blog/_astro/persistent-cookie.C_TG_vEa_Z1X9IoL.webp 2001w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;A few points to note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The PingOne Protect Evaluation nodes uses the default &lt;code&gt;Risk Policy Set ID.&lt;/code&gt; As a best practice a new risk policy should be created and the ID set in this parameter. From the PingOne Console &amp;gt; Expand Threat Protection &amp;gt; Risk Policies &amp;gt; Global Policies Blue + &amp;gt; Set a name &amp;gt; Configure as required &amp;gt; Apply &amp;gt; Copy the resulting Policy ID into this parameter. For example &lt;code&gt;a05cccb8-ada5–44e3-a6cd-cefdbc0c942f&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Additional &lt;code&gt;BOT_MITIGATION&lt;/code&gt; and &lt;code&gt;AITM_MITIGATION&lt;/code&gt; (Adversary-In-The-Middle) &lt;a href=&quot;https://docs.pingidentity.com/auth-node-ref/latest/pingone/pingone-protect-evaluation.html&quot;&gt;items&lt;/a&gt; were added to the Recommended Actions parameter of the PingOne Protect Evaluation nodes to demonstrate the node’s flexibility in mapping specific risk results to additional outcomes.&lt;/li&gt;
&lt;li&gt;The PingOne Protect Result node captures additional context just before the session is created, ensuring maximum accuracy for future assessment of the user’s risk posture and behaviour.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;strong&gt;Journey Breakdown&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;This journey demonstrates how to implement a “Remember Me” (persistent login) flow enhanced with PingOne Protect risk evaluation.&lt;br /&gt;
It combines persistent cookie management with adaptive risk analysis to allow seamless logins for low-risk sessions while enforcing re-authentication for higher-risk ones.&lt;/p&gt;
&lt;p&gt;On every execution of the journey, the PingOne Protect Initialize node runs first to begin context and device data capture.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The journey then checks for the presence of a persistent cookie named &lt;code&gt;persist-session&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If persist-session is found:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The PingOne Protect Evaluation node calls PingOne Protect to evaluate the context and calculate risk.&lt;/li&gt;
&lt;li&gt;For a Low risk outcome, the PingOne Protect Result node is invoked, and seamless “Remember Me” functionality is preserved.&lt;/li&gt;
&lt;li&gt;For any other outcome, the persistent cookie is removed, the PingOne Protect Risk value stored in nodeState is displayed for insight, and the user is redirected to authenticate again.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If persist-session is not found:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The user is prompted for their username and password, and the PingOne Protect Evaluation node again evaluates context and risk.&lt;/li&gt;
&lt;li&gt;If a Low risk outcome is returned, a new persistent cookie is set, enabling “Remember Me” on future logins.&lt;/li&gt;
&lt;li&gt;For all other outcomes (except ClientError, which shows an error message), the PingOne Protect Risk value is displayed for eudcation and understanding.&lt;/li&gt;
&lt;li&gt;Login is still permitted — the session proceeds, but without setting the persistent cookie — ensuring user access is not disrupted, only “Remember Me” functionality is withheld.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;Check out the video below for a live demo&lt;/p&gt;

&lt;p&gt;PingOne Protect offers powerful monitoring and visual &lt;a href=&quot;https://docs.pingidentity.com/pingone/threat_protection_using_pingone_protect/p1_protect_dashboard.html&quot;&gt;dashboards&lt;/a&gt; that translate complex security data into clear, actionable insights. With real-time visibility into analysed events, risk trends, geolocation anomalies and high-risk user behaviour, organisations can quickly assess and respond to emerging threats. Here’s an example dashboard:
&lt;img alt=&quot;PingOne Protect Visual Dashboard&quot; loading=&quot;lazy&quot; width=&quot;3562&quot; height=&quot;2076&quot; src=&quot;https://developer.pingidentity.com/blog/_astro/dashboard.B3kzHsgZ_Z19ggXV.webp&quot; srcset=&quot;https://developer.pingidentity.com/blog/_astro/dashboard.B3kzHsgZ_Z29wmHa.webp 640w, https://developer.pingidentity.com/blog/_astro/dashboard.B3kzHsgZ_1iUuEq.webp 750w, https://developer.pingidentity.com/blog/_astro/dashboard.B3kzHsgZ_8M9EV.webp 828w, https://developer.pingidentity.com/blog/_astro/dashboard.B3kzHsgZ_3hfOy.webp 1080w, https://developer.pingidentity.com/blog/_astro/dashboard.B3kzHsgZ_1sP9Cq.webp 1280w, https://developer.pingidentity.com/blog/_astro/dashboard.B3kzHsgZ_ZJHvqL.webp 1668w, https://developer.pingidentity.com/blog/_astro/dashboard.B3kzHsgZ_1SSLw0.webp 2048w, https://developer.pingidentity.com/blog/_astro/dashboard.B3kzHsgZ_Z1FsYxz.webp 2560w, https://developer.pingidentity.com/blog/_astro/dashboard.B3kzHsgZ_Z19ggXV.webp 3562w&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The integration of PingOne Protect with PingOne Advanced Identity Cloud elevates a simple “Remember Me” feature into an intelligent, adaptive security capability. By continuously evaluating contextual and behavioural risk, it ensures that convenience does not come at the expense of protection.&lt;/p&gt;
&lt;p&gt;Users enjoy seamless, low-friction logins when their behaviour is trusted, while higher-risk sessions automatically trigger the right level of friction — discreetly and without disruption. The result is a smarter, more secure “Remember Me” experience that balances usability with robust risk-based control — all achieved through a no-code, out-of-the-box Ping Identity platform implementation.&lt;/p&gt;</content:encoded><media:content url="https://developer.pingidentity.com/blog/_astro/title-image.CTi6Rdvk_Z15JE7d.webp" medium="image" type="image/webp" width="1480" height="740"/></item></channel></rss>