<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michał Silski</title>
    <description>The latest articles on DEV Community by Michał Silski (@melmanm).</description>
    <link>https://dev.to/melmanm</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1020840%2Fd5aa0b14-6a46-4cca-8e01-dd8064cea398.png</url>
      <title>DEV Community: Michał Silski</title>
      <link>https://dev.to/melmanm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/melmanm"/>
    <language>en</language>
    <item>
      <title>Auth0: using system browser to authenticate in .NET desktop application</title>
      <dc:creator>Michał Silski</dc:creator>
      <pubDate>Sat, 04 Mar 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/melmanm/auth0-using-system-browser-to-authenticate-in-net-desktop-application-1f6j</link>
      <guid>https://dev.to/melmanm/auth0-using-system-browser-to-authenticate-in-net-desktop-application-1f6j</guid>
      <description>&lt;p&gt;In &lt;a href="https://melmanm.github.io/misc/2023/02/13/article6-oauth20-authorization-in-desktop-applicaions.html"&gt;previous article&lt;/a&gt; I described general ideas on how to integrate OAuth 2.0 authorization and authentication with desktop applications. In this article I will describe how to implement authentication in .NET desktop application with Auth0, using default system browser to perform user’s login and logout actions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Auth0 nuget packages

&lt;ul&gt;
&lt;li&gt;Default implementation.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Display auth0 login form in system browser

&lt;ul&gt;
&lt;li&gt;Desired architecture&lt;/li&gt;
&lt;li&gt;Custom IBrowser implementation&lt;/li&gt;
&lt;li&gt;Implementation considerations&lt;/li&gt;
&lt;li&gt;Redirect URI&lt;/li&gt;
&lt;li&gt;HttpListener instance&lt;/li&gt;
&lt;li&gt;Timeout&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Auth0 nuget packages
&lt;/h2&gt;

&lt;p&gt;Auth0 provides &lt;code&gt;Auth0.OidcClient.WPF&lt;/code&gt; and &lt;code&gt;Auth0.OidcClient.WinForms&lt;/code&gt; nuget packages, which enable developers to integrate Auth0 authorization and authentication with WPF and WinForms-based applications. Example below presents minimal code, needed to authenticate user and acquire id_token using &lt;code&gt;Auth0.OidcClient.WPF&lt;/code&gt; library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="c1"&gt;//application domain&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="c1"&gt;//application clientId&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Auth0Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Auth0ClientOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ClientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;loginResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoginAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;id_token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loginResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IdentityToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userClaims&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loginResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Authorization code flow with PKCE is used to obtain a token&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Auth0ClientOptions&lt;/code&gt;class includes &lt;code&gt;IBrowser Browser {get; set;}&lt;/code&gt; property. &lt;code&gt;IBrowser&lt;/code&gt; implementation takes responsibility of displaying login screen to the user. Additionally, it is used to perform user logout process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Default implementation.
&lt;/h3&gt;

&lt;p&gt;By default, Auth0 nuget package uses &lt;code&gt;WebViewBrowser&lt;/code&gt; implementation of &lt;code&gt;IBrowser&lt;/code&gt; interface. It makes use of &lt;a href="https://learn.microsoft.com/en-us/windows/communitytoolkit/controls/wpf-winforms/webviewcompatible"&gt;&lt;code&gt;WebViewCompatible&lt;/code&gt;&lt;/a&gt; framework component, to display login form to the user.&lt;code&gt;WebViewBrowser&lt;/code&gt; renders html login page in new application window &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RmcTQz8k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article7/auth0-inapp-login.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RmcTQz8k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article7/auth0-inapp-login.png" alt="auth0-default-login" width="647" height="698"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Display Auth0 login form in system browser
&lt;/h2&gt;

&lt;p&gt;In this section presents custom implementation of &lt;code&gt;IBrowser&lt;/code&gt; interface, which displays login form using system browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Desired architecture
&lt;/h3&gt;

&lt;p&gt;In order to display login form, system browser should be launched by desktop application, with authorization request configured as startup URL. In response, authorization server returns login page.&lt;/p&gt;

&lt;p&gt;Once user is authenticated, application has to capture redirect URI, with authorization code provided as parameter. Since system browser is running on client machine, it can successfully resolve localhost loopback address. Application can host http server on one of the localhost ports e.g. &lt;code&gt;http://localhost:8888&lt;/code&gt;. Once localhost address is set as redirect URI on authorization server and specified in initial authorization request, authorization code can be captured by the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OKHe22fK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article6/system-browser-flow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OKHe22fK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article6/system-browser-flow.png" alt="system-browser-flow" width="880" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom IBrowser implementation
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;IBrowser&lt;/code&gt; interface includes a single method signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IBrowser&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BrowserResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BrowserOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BrowserOptions&lt;/code&gt;, passed as an argument, specifies &lt;code&gt;StartUrl&lt;/code&gt; and &lt;code&gt;EndUrl&lt;/code&gt; properties&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;StartUrl&lt;/code&gt; - URL to initiate OAuth action. In case of authentication or authorization it is set to authorize request. (In the context of logout it represents &lt;code&gt;/logout&lt;/code&gt; endpoint request.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EndUrl&lt;/code&gt; - Redirect URL. (In case of logout it represents post logout redirect url.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below, custom, &lt;code&gt;IBrowser&lt;/code&gt; implementation utilizes system browser to perform login and logout actions&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SystemBrowser&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IBrowser&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ERROR_MESSAGE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Error ocurred."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SUCCESSFUL_AUTHENTICATION_MESSAGE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"You have been successfully authenticated. You can now continue to use desktop application."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SUCCESSFUL_LOGOUT_MESSAGE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"You have been successfully logged out."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;HttpListener&lt;/span&gt; &lt;span class="n"&gt;_httpListener&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;StartSystemBrowser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;startUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProcessStartInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;UseShellExecute&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BrowserResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BrowserOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;StartSystemBrowser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BrowserResult&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;//abort _httpListener if exists&lt;/span&gt;
        &lt;span class="n"&gt;_httpListener&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_httpListener&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpListener&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;listenUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EndUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;//HttpListenerContext require uri ends with /&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;listenUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EndsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;listenUrl&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="n"&gt;_httpListener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prefixes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listenUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;_httpListener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_httpListener&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;HttpListenerContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_httpListener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetContextAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="c1"&gt;//if _httpListener is aborted while waiting for response it throws HttpListenerException exception&lt;/span&gt;
                &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpListenerException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResultType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BrowserResultType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnknownError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c1"&gt;//set result response url&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AbsoluteUri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="c1"&gt;//generate message displayed in the browser, and set resultType based on request&lt;/span&gt;
                &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;displayMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;displayMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SUCCESSFUL_AUTHENTICATION_MESSAGE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResultType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BrowserResultType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AbsoluteUri&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EndUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;displayMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SUCCESSFUL_LOGOUT_MESSAGE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResultType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BrowserResultType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;displayMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ERROR_MESSAGE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResultType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BrowserResultType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnknownError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c1"&gt;//return message to be displayed in the browser&lt;/span&gt;
                &lt;span class="n"&gt;Byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;displayMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentLength64&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutputStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutputStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;_httpListener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once authorization code is captured, system browser displays&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HSabQqZ3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article7/successful-authentication.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HSabQqZ3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article7/successful-authentication.jpg" alt="system-browser-flow" width="663" height="69"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After successful logout, system browser displays&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A8NcjtVW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article7/successful-logout.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A8NcjtVW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article7/successful-logout.jpg" alt="system-browser-flow" width="305" height="73"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SystemBrowser&lt;/code&gt; class can be used to instantiate &lt;code&gt;Auth0ClientOptions&lt;/code&gt; as below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Auth0Client&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Auth0Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Auth0ClientOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="c1"&gt;//application domain&lt;/span&gt;
    &lt;span class="n"&gt;ClientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="c1"&gt;//application clientId&lt;/span&gt;
    &lt;span class="n"&gt;Browser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SystemBrowser&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;RedirectUri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:8888"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PostLogoutRedirectUri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:8888/logout"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Login&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;loginResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoginAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;id_token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loginResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IdentityToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userClaims&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loginResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that given &lt;code&gt;RedirectUri&lt;/code&gt; and &lt;code&gt;PostLogoutRedirectUri&lt;/code&gt; need to be registered in Auth0 application settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation considerations
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Redirect URI
&lt;/h4&gt;

&lt;p&gt;Proposed implementation arbitrary specifies redirect URI as &lt;code&gt;http://localhost:8888&lt;/code&gt;. It can be considered risky to assume port &lt;code&gt;8888&lt;/code&gt; is not occupied by other process in application’s target environment. In case port is already in use, authentication could not be performed.&lt;/p&gt;

&lt;p&gt;To mitigate this risk, implementation could verify, if desired port is not occupied. In case it is occupied, other port can be verified and eventually used i.e. &lt;code&gt;8889&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note that all possible redirect URIs needs to be registered in Auth0 application settings. As for now Auth0 does not provide an option to specify redirect URI port using wildcard (i.e. &lt;code&gt;http://localhost:*&lt;/code&gt;). However, it is possible to register multiple localhost redirect URIs with a certain port range. Application can try to find a free port within the registered range.&lt;/p&gt;

&lt;h4&gt;
  
  
  HttpListener instance
&lt;/h4&gt;

&lt;p&gt;In presented example, &lt;code&gt;SystemBrowser&lt;/code&gt; holds an instance of &lt;code&gt;HttpListener&lt;/code&gt;. Before the instance is initialized, there is &lt;code&gt;_httpListener?.Abort();&lt;/code&gt; executed, to close and dispose existing &lt;code&gt;HttpListener&lt;/code&gt; instance (if there is any). This mechanism is intended for scenarios, where user initiates login, but does not complete it. In subsequent login attempt existing listener will be aborted first, before initializing new instance. &lt;code&gt;Abort()&lt;/code&gt; causes &lt;code&gt;HttpListenerException&lt;/code&gt; in original thread, so it needs to be handled properly.&lt;/p&gt;

&lt;h4&gt;
  
  
  Timeout
&lt;/h4&gt;

&lt;p&gt;Presented example, for sake of simplicity, does not consider timeout. &lt;code&gt;BrowserOptions&lt;/code&gt;, passed as input parameter, contains &lt;code&gt;public TimeSpan Timeout { get; set; }&lt;/code&gt; property. If needed, &lt;code&gt;HttpListener&lt;/code&gt; can be closed, if no request was captured within given timeout.&lt;/p&gt;

</description>
      <category>oauth20</category>
      <category>authentication</category>
      <category>auth0</category>
    </item>
    <item>
      <title>OAuth 2.0 authorization with desktop application</title>
      <dc:creator>Michał Silski</dc:creator>
      <pubDate>Mon, 13 Feb 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/melmanm/oauth-20-authorization-in-desktop-applications-1dib</link>
      <guid>https://dev.to/melmanm/oauth-20-authorization-in-desktop-applications-1dib</guid>
      <description>&lt;p&gt;This article presents challenges and solutions for integration of OAuth 2.0 authorization with desktop applications. Vast majority of OAuth 2.0 compliant authorization servers lead user through authentication and consent process using html-based web form. This article shows how to seamlessly integrate this process into desktop application, to obtain id or/and access token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Desktop application

&lt;ul&gt;
&lt;li&gt;Challenges&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Solution 1: Native web browser component

&lt;ul&gt;
&lt;li&gt;Redirect URI&lt;/li&gt;
&lt;li&gt;Security Consideration&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Solution 2. System Web Browser

&lt;ul&gt;
&lt;li&gt;Http or Https&lt;/li&gt;
&lt;li&gt;Redirect URI&lt;/li&gt;
&lt;li&gt;User experience&lt;/li&gt;
&lt;li&gt;SSO&lt;/li&gt;
&lt;li&gt;Security Consideration&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Dedicated libraries&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Desktop application
&lt;/h2&gt;

&lt;p&gt;Desktop application should be considered as public (non-confidential) client. During installation, all application’s binaries and files are copied into local file system. Since they can be easily decompiled and inspected by anyone having an access to file system, application should not contain any secrets. In this context, desktop application is similar to SPA web application, where JavaScript code can be entirely inspected.  &lt;/p&gt;

&lt;p&gt;It is highly &lt;a href="https://www.rfc-editor.org/rfc/rfc8252#section-6"&gt;recommended&lt;/a&gt; to use Authorization Code grant flow with PKCE extension, to authorize user with Desktop application. PKCE is designed for public clients and doesn’t require storing any secrets on user’s device. &lt;/p&gt;

&lt;p&gt;While performing on user’s behalf authorization, authorization server displays a form, where users can enter their credentials. Authorization servers return html-based form in response to &lt;em&gt;/authorize&lt;/em&gt; request, which initiated OAuth grant flow.&lt;/p&gt;

&lt;p&gt;Desktop application has to display html-based form to the user. Once user is authenticated, application gathers authorization code. Authorization code is needed to obtain access/id token from /token endpoint.&lt;/p&gt;

&lt;p&gt;Authorization server returns authorization code, as a parameter, within a front channel call to redirect URI. Redirect URI is specified in initial &lt;em&gt;/authorize&lt;/em&gt; request as parameter. To prevent &lt;a href="https://oauth.net/advisories/2014-1-covert-redirect/"&gt;open redirector attack&lt;/a&gt;, redirect URI needs to be first configured on authorization server in application registration settings.  &lt;/p&gt;

&lt;p&gt;Gathering authorization code from redirect URI, becomes a challenge for desktop application, since normally it doesn’t operate in web domain and can’t be reached by URI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenges
&lt;/h3&gt;

&lt;p&gt;There are two main challenges that needs to be addressed to integrate desktop application with OAuth 2.0 authorization server.  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;How to display html-based authorize form&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How to gather authorization code, sent to redirect URI&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Solution 1. Native web browser component
&lt;/h2&gt;

&lt;p&gt;Many desktop frameworks provide a dedicated web browser component. It can be embedded in another control or displayed in separate application window. Application code can control browser component behavior and react to its events.&lt;/p&gt;

&lt;p&gt;Application can subscribe to web browser component event, which is raised after address in navigation bar is changed. For instance, .NET &lt;em&gt;WebView2&lt;/em&gt; control provides &lt;a href="https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/navigation-events"&gt;&lt;em&gt;NavigationStarting&lt;/em&gt;&lt;/a&gt; event, triggered when&lt;/p&gt;

&lt;p&gt;&lt;em&gt;WebView2 starts to navigate and the navigation results in a network request. The host may disallow the request during the event.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;By subscribing to this event, application can intercept each change of browser navigation URI. Once URI matches redirect URI, application can capture authorization code provided as parameter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XHA2WIfW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article6/native-browser-flow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XHA2WIfW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article6/native-browser-flow.png" alt="native-browser-component" width="843" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below C# demo code presents how WebView2 control can be used to handle OAuth 2.0 authorization, using Authorization code flow with PKCE.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;authorizeRequest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateAuthorizeRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
  &lt;span class="n"&gt;webView2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CoreWebView2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authorizeRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

  &lt;span class="c1"&gt;//subscribe NavigationStarting event &lt;/span&gt;
  &lt;span class="n"&gt;webView2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NavigationStarting&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
  &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_redirectUri&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; 
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

    &lt;span class="c1"&gt;//get code parameter from redirect uri  &lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HttpUtility&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParseQueryString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

    &lt;span class="c1"&gt;//request token using authorization code &lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;//use token to access resources&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;CreateAuthorizeRequest() and GetToken(string token) methods implementation is not shown for simplicity.&lt;/em&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Redirect URI
&lt;/h3&gt;

&lt;p&gt;Application doesn't require redirect URI to be reached. It takes advantage from browser just trying to navigate to it. Since it is not required to be reachable, it can be any, even not existing, URI. It only needs to be configured as allowed redirect URI on authorization server. &lt;/p&gt;

&lt;p&gt;Authorization server providers usually add default redirect URI, when developer registers desktop application. It is usually a neutral domain, which belongs to provider. It most likely returns empty html page with 200 status code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Consideration
&lt;/h3&gt;

&lt;p&gt;Native browser component, usually doesn't display address bar. Even it is displayed, its content is entirely controlled by desktop application. It makes users unable to verify if they are signing-in to legitimate authorization server. Authorization server address and its https certificate, should be always verified by the user to mitigate phishing attacks. Even if your application is trusted, it is not good practice to make users used to such design.&lt;/p&gt;

&lt;p&gt;Embedding sing-in form in desktop application, violates the principle of least privilege. Since application entirely controls browser component, it can get an access to user credentials. For instance, it can be achieved, by capturing keystrokes while sign-in form is displayed. Username and password can be used by application to obtain more privileged accesses to resources, without user consent. Again, even if your application is trusted, it is not good practice to make users used to design, which can be harmful in case of dealing with malicious application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 2. System Web Browser
&lt;/h2&gt;

&lt;p&gt;Alternatively, default system browser (like chrome, firefox, edge) can be used. &lt;/p&gt;

&lt;p&gt;In order to display login form, system browser can be launched by desktop application, with &lt;em&gt;/authorize&lt;/em&gt; request configured as startup URI. Unfortunately, application logic is not able to react on system browser's navigation events, like in Solution 1.&lt;/p&gt;

&lt;p&gt;Though application can host its own http server endpoint available at redirect URI address. Since system web browser is running on client machine, it can successfully resolve localhost loopback address. Considering that, application can host http server on one of the localhost ports e.g. &lt;code&gt;http://localhost:8888&lt;/code&gt;. Once localhost address is set as redirect URI on authorization server and specified in &lt;em&gt;/authorize&lt;/em&gt; request, authorization response containing code can be captured by localhost server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OKHe22fK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article6/system-browser-flow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OKHe22fK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article6/system-browser-flow.png" alt="system-browser-component" width="880" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below C# demo code shows how authorization code can be captured by local server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt; 
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;redirectUri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:8888/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;authorizeRequest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateAuthorizeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

   &lt;span class="c1"&gt;//start system browser &lt;/span&gt;
   &lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProcessStartInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authorizeRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;UseShellExecute&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; 

   &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpListener&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
   &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prefixes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
   &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

   &lt;span class="c1"&gt;//wait for server captures redirect_uri  &lt;/span&gt;
   &lt;span class="n"&gt;HttpListenerContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
   &lt;span class="n"&gt;HttpListenerRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

   &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
   &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

   &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
   &lt;span class="c1"&gt;//use token to access resources&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;CreateAuthorizeRequest() and GetToken(string token) methods are not shown for simplicity.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Http or Https
&lt;/h3&gt;

&lt;p&gt;Since the redirect never leaves the user’s PC it is acceptable to use &lt;code&gt;http://localhost:8888&lt;/code&gt; instead of &lt;code&gt;https://localhost:8888&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redirect URI
&lt;/h3&gt;

&lt;p&gt;Some authorization servers allow to register localhost redirect URIs with the port wildcard (like &lt;code&gt;http://localhost:*&lt;/code&gt;) or accepts all different ports if only &lt;code&gt;http://localhost&lt;/code&gt; is register. It is very useful for desktop applications running inside user’s operating systems. Some ports can be already taken by other processes. If intended port is taken, application is able to host http server on different port.&lt;/p&gt;

&lt;h3&gt;
  
  
  User experience
&lt;/h3&gt;

&lt;p&gt;It is recommended to avoid closing web browser after token is obtained. If system browser is already opened on user’s device, an attempt to launch specific URL, results in opening new tab in existing window. Termination of entire browser process, closes all tabs, which leads to rather poor user experience. Instead, localhost server can return html page, with a text confirming successful authorization. For instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You have been successfully authorized. You can now continue to use desktop application.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SSO
&lt;/h3&gt;

&lt;p&gt;System browser usage can leverage SSO experience. Single sign-on (SSO) mechanism allows the same user session to be used by multiple applications. It provides great user experience, since user can be automatically signed-in to multiple applications, registered in authorization server, after single authentication. It is possible, thanks to user session cookie, which is stored in user browser after successful login. Session cookie is then sent to authorization server with subsequent requests. It can be then recognized and validated. Once it is valid, authorization server assumes user session is active. In result login form is not displayed again.&lt;/p&gt;

&lt;p&gt;System browser enables desktop applications to share user session with web applications, registered under the same authorization server. (Authorization servers often allow to configure group of applications allowed to share the same user session).&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Consideration
&lt;/h3&gt;

&lt;p&gt;Using system browser, to authorize desktop application’s user, involves a localhost server endpoint. In case loopback interface is accessible by other application, authorization code can be intercepted. To prevent it, applications should implement PKCE. It protects the intercepted authorization code from being used to obtain a token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System browser, is OAuth 2.0 recommended solution for authorizing users in desktop applications.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Dedicated libraries
&lt;/h2&gt;

&lt;p&gt;Authorization server providers often distribute code libraries dedicated to their products. If dedicated library is available, it is highly recommended to use it. It makes implementation easier and ensures it is compatible with specific authorization server. &lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Table below compares key features of presented solution &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Native browser component&lt;/th&gt;
&lt;th&gt;System browser&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User experience&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Good. Authorization is entirely performed in desktop application.&lt;/td&gt;
&lt;td&gt;Medium. User is navigated to system browser to authorize. After process in finished user needs to navigate back to desktop application.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User is not able to verify authorization server address and certificate.&lt;/td&gt;
&lt;td&gt;OAuth 2.0 recommendation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSO&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No, partially depends on framework and component&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Support in popular Authorization server providers libraries&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In &lt;a href="https://melmanm.github.io/misc/2023/03/04/article7-auth0-destkop-application-authentication-with-system-browser.html"&gt;next article&lt;/a&gt; I will present how to implement system browser login in WPF and WinForms with Auth0.&lt;/p&gt;

</description>
      <category>oauth20</category>
      <category>authorization</category>
      <category>openid</category>
      <category>identity</category>
    </item>
    <item>
      <title>Azure AD B2C session management with MSAL and React.js - Part 2.</title>
      <dc:creator>Michał Silski</dc:creator>
      <pubDate>Wed, 01 Feb 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/melmanm/azure-ad-b2c-session-management-with-msal-and-reactjs-part-2-2c9a</link>
      <guid>https://dev.to/melmanm/azure-ad-b2c-session-management-with-msal-and-reactjs-part-2-2c9a</guid>
      <description>&lt;p&gt;This article continues the series, related with session management solutions in Azure AD B2C. &lt;a href="https://melmanm.github.io/misc/2023/01/31/article4-azure-ad-b2c-session-management-with-MSAL-and-react-js-part1.html" rel="noopener noreferrer"&gt;Previous post&lt;/a&gt; outlined polling-based approach to determine session status in SSO scope. Today I will focus on &lt;strong&gt;front-channel logout&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As the article goal is to inspect session management from application perspective, I will refer to the code samples. Code samples originate from to React.js SPA application, supported by MSAL.js library.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Full application code is available on my &lt;a href="https://github.com/melmanm/react-js-azure-b2c-session-management-sample" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Front-channel logout&lt;/li&gt;
&lt;li&gt;Configuration of Azure AD B2C&lt;/li&gt;
&lt;li&gt;MSAL and React.js configuration&lt;/li&gt;
&lt;li&gt;Third party LocalStorage access&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Front-channel logout
&lt;/h2&gt;

&lt;p&gt;Front-channel logout idea is based on user logout notification, broadcasted by Azure AD B2C to all applications sharing the same user session. Usually, in response to the notification, application drops current user context and redirect user to login page (to indicate reauthentication is required).&lt;/p&gt;

&lt;p&gt;Front-channel logout procedure starts after one application in SSO scope initiates &lt;a href="https://openid.net/specs/openid-connect-rpinitiated-1_0.html" rel="noopener noreferrer"&gt;OpenId Connect logout procedure&lt;/a&gt;, by sending a request to Azure AD B2C &lt;em&gt;/logout&lt;/em&gt; endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET &amp;lt;tenantname.b2clogin.co/&amp;lt;tenantname&amp;gt;.onmicrosoft.com/&amp;lt;policy_name&amp;gt;/oauth2/v2.0/logout 
?post_logout_redirect_uri=&amp;lt;redirect uri, application will be redirected to this uri after logout once finishes&amp;gt; 
&amp;amp;id_token_hint=&amp;lt;id_token_hint&amp;gt; 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below diagram shows the procedure of front-channel logout in SSO environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fu232pwnn2r5vm6xmt8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fu232pwnn2r5vm6xmt8.png" alt="front channel logout" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After &lt;em&gt;/logout&lt;/em&gt; request, Azure AD B2C recognizes all applications, which use the same session as one passed as a cookie within the request. Next, it creates a list of their front-channel logout URLs.&lt;/p&gt;

&lt;p&gt;In response to &lt;em&gt;/logout&lt;/em&gt; request, B2C returns a page, which loads an iframe. Iframe includes JavaScript logic calling all URLs listed by Azure AD B2C.&lt;/p&gt;

&lt;p&gt;Finally, user is redirected to &lt;em&gt;post_logout_redirect_uri&lt;/em&gt; specified in the original request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration of Azure AD B2C
&lt;/h2&gt;

&lt;p&gt;To enable front-channel logout in Azure AD B2C, following steps needs to be done&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Front-channel logout URL&lt;/strong&gt; has to be configured for applications registrations. Given application URL will be called by Azure AD B2C, when any other application in SSO domain initializes logout for the user, who utilizes the same session. Setting is available under &lt;em&gt;App registration-&amp;gt;authentication&lt;/em&gt;. URL needs to be HTTPS for security reasons. Additionally, URL cannot contain fragment component (like example.com#logout). &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbtus5ucbkts780kz3hh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbtus5ucbkts780kz3hh.jpg" alt="front-channel URL" width="721" height="115"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Custom Policy Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Note that front-channel logout is supported with Custom Policies only.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Custom policy &lt;em&gt;UserJourney&lt;/em&gt; needs to be decorated with &lt;em&gt;UserJourneyBehaviors&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Scope of &lt;em&gt;SingleSignOn&lt;/em&gt; can be set to &lt;strong&gt;Tenant&lt;/strong&gt;, &lt;strong&gt;Application&lt;/strong&gt; or &lt;strong&gt;Policy&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;EnforceIdTokenHint&lt;/em&gt; option is required, so the current user context is always passed within /logout request to Azure AD B2C. Note that front-channel logout is supported only with Custom Policies.
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;UserJourneyBehaviors&amp;gt;&lt;/span&gt; 
    &lt;span class="nt"&gt;&amp;lt;SingleSignOn&lt;/span&gt; &lt;span class="na"&gt;Scope=&lt;/span&gt;&lt;span class="s"&gt;"Tenant"&lt;/span&gt; &lt;span class="na"&gt;EnforceIdTokenHintOnLogout=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;/UserJourneyBehaviors&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  MSAL and React.js configuration
&lt;/h2&gt;

&lt;p&gt;React.js &amp;amp; MASL application requires following configuration&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MSAL configuration&lt;/strong&gt; – MSAL configuration is expressed in JSON format. It is used to instantiate MASL application instance. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;allowRedirectInIframe: true&lt;/em&gt; – application needs to be reached from iframe, rendered in Azure AD B2C tenant domain. This property ensures &lt;em&gt;X-Frame-Options: DENY&lt;/em&gt; is not added in http header. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;cacheLocation: "localStorage"&lt;/em&gt; - MSAL stores user related information in browser storage.
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msalInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PublicClientApplication&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;  
&lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
     &lt;span class="c1"&gt;//auth config  &lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
     &lt;span class="na"&gt;cacheLocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localStorage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
     &lt;span class="na"&gt;allowRedirectInIframe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  
&lt;span class="p"&gt;}})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Logout endpoint&lt;/strong&gt; – application needs to provide a logout endpoint, at the URL corresponding with Azure AD B2C registration. React.js framework is not natively prepared for handleing routing, however uisng React-router, it is possible to serve specific content on &lt;em&gt;/logout&lt;/em&gt; endpoint. Using &lt;em&gt;BrowserRouter&lt;/em&gt; component, &lt;em&gt;/logout&lt;/em&gt; request can be routed to specific application component.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;BrowserRouter&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Routes&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Route&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt; &lt;span class="na"&gt;element=&lt;/span&gt;&lt;span class="s"&gt;{&amp;lt;Logout&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;} /&amp;gt;
        &lt;span class="nt"&gt;&amp;lt;Route&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;element=&lt;/span&gt;&lt;span class="s"&gt;{&amp;lt;Home&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;} /&amp;gt;
    &lt;span class="nt"&gt;&amp;lt;/Routes&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/BrowserRouter&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Logout component should not contain much logic. It needs to perform well. Note that once user logs out from other application in SSO domain, component is loaded using front-channel request. The longer it takes to load, the longer user waits for Azure AD B2C to complete original logout request. Logout component can be designed as follows:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useMsal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@azure/msal-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Logout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMsal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

    &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logoutRedirect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
        &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveAccount&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; 
        &lt;span class="na"&gt;onRedirectNavigate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; 
    &lt;span class="p"&gt;});&lt;/span&gt; 

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; 
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; 
    &lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;    
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Setting &lt;em&gt;onRedirectNavigate: false&lt;/em&gt; ensures that only local logout will be performed. Once onRedirectNavigate is set to true, local logout is followed by calling &lt;em&gt;/logout&lt;/em&gt; endpoint of Azure AD B2C.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Handle account storage events&lt;/strong&gt; – MSAL stores its internal status, including current user related data, in browser storage (localStorage – according to configured). Since the same application can be opened in many browser tabs/windows, MSAL has to react on changes in localStorage entries. In result all tabs/windows with the same application opened, can synchronize current user context. To enable this mechanism, following function needs to be executed on PublicClientApplication.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;msalInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enableAccountStorageEvents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Handling storage events is also critical in the context of front-channel logout&lt;/strong&gt;. After application’s &lt;em&gt;/logout&lt;/em&gt; endpoint is called by Azure AD B2C iframe, user context is removed from localStorage. If application is opened in different browser tab or window, it can to be notified about this change.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Third party LocalStorage access
&lt;/h2&gt;

&lt;p&gt;Application logout endpoint, loaded from Azure AD B2C iframe, needs to access localStorage. Unless Azure AD B2C is configured using &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-domain?pivots=b2c-custom-policy" rel="noopener noreferrer"&gt;Azure Front Door&lt;/a&gt;, to be available from the same domain as application, it can lead to some difficulties.&lt;/p&gt;

&lt;p&gt;Some browsers, controls localStorage access using the third-party cookies settings. Once it is disabled, localStorage cannot be accessed from website iframed in a different domain. Since more and more browsers restricts cross domain access to storage, it needs to be considered. &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3g8781yatpnybxvnkpo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3g8781yatpnybxvnkpo.jpg" alt="3rd party cookie" width="679" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azureadb2c</category>
      <category>oauth</category>
      <category>identity</category>
      <category>react</category>
    </item>
    <item>
      <title>Azure AD B2C session management with MSAL and React.js - Part 1.</title>
      <dc:creator>Michał Silski</dc:creator>
      <pubDate>Tue, 31 Jan 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/melmanm/azure-ad-b2c-session-management-with-msal-and-reactjs-part-1-45ij</link>
      <guid>https://dev.to/melmanm/azure-ad-b2c-session-management-with-msal-and-reactjs-part-1-45ij</guid>
      <description>&lt;p&gt;&lt;a href="https://melmanm.github.io/misc/2023/01/30/article3-openid-connect-session-management.html"&gt;Previous article&lt;/a&gt; describes session management possibilities provided by OpenId Connect. OpenId-Connect &lt;strong&gt;session&lt;/strong&gt; represents the authenticated user context, maintained between Applications, running on users device, and Identity Provider Server. Vast majority of modern web application works in the context of logged-in user. OAuth-based solutions delegate identity management to external Identity Provider Server. From application perspective it is important to be synchronized with Identity Provider and be able to react on user session state changes. It becomes even more relevant, if multiple applications operate in the context of the same user session (SSO solutions).&lt;/p&gt;

&lt;p&gt;In this article I will describe Azure AD B2C-specific approach to session management in SSO environment. As the article goal is to inspect session management from developer perspective, I will refer to the code samples. Code samples originate from React.js SPA application, supported by MSAL.js library.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Full application code is available on my &lt;a href="https://github.com/melmanm/react-js-azure-b2c-session-management-sample"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MSAL&lt;/li&gt;
&lt;li&gt;SSO- Single sign-on and Single sign-out&lt;/li&gt;
&lt;li&gt;Azure AD B2C supported session management methods&lt;/li&gt;
&lt;li&gt;
/authorize endpoint polling

&lt;ul&gt;
&lt;li&gt;User Experience&lt;/li&gt;
&lt;li&gt;React to session expiration&lt;/li&gt;
&lt;li&gt;Third party cookies&lt;/li&gt;
&lt;li&gt;Azure AD B2C API rate limits&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;To be continued&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  MSAL
&lt;/h2&gt;

&lt;p&gt;MSAL (Microsoft Authentication Library) is an open-source library, which enables developers to utilize OAuth 2.0 based services like Azure AD or Azure AD B2C. MSAL provides number of functions designed to execute OAuth 2.0 flows and acquire tokens. MSAL helps application developers with user management, by supporting login, logout, edit profile or change password user flows. MASL introduces the user context to the application. User context is stored in browser storage. It enables applications to maintain their own user-related cookies layer.&lt;/p&gt;

&lt;p&gt;MSAL is available on many platforms (MSAL.js, MSAL for Java, MSAL.NET and more)&lt;/p&gt;

&lt;h2&gt;
  
  
  SSO- Single sign-on and Single sign-out
&lt;/h2&gt;

&lt;p&gt;Single sign-on and single sign-out functionalities are important in the context of user session management. They allow to synchronize user session status across many applications launched in the context of the same browser. SSO improves user experience by bypassing frequent re-authentications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Single sign-on
&lt;/h3&gt;

&lt;p&gt;Azure AD B2C single sign-on (SSO) mechanism allows the same user session to be used by multiple applications. It provides great user experience, since user can be automatically signed in to multiple applications, after single authentication. It is possible, thanks to user session cookie, which is stored in user browser after successful login. Session cookie is sent to Azure AD B2C with subsequent requests, where can be recognized and validated. Once it is valid, Azure AD B2C assumes user session is active. In result login form is not displayed again.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSO scope
&lt;/h3&gt;

&lt;p&gt;Azure AD B2C allows to configure applications that are allowed to share the same user session. More information on how to configure Azure AD B2C can be found in &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory-b2c/session-behavior?pivots=b2c-custom-policy#configure-azure-ad-b2c-session-behavior"&gt;Microsoft docs&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Single sign-out
&lt;/h3&gt;

&lt;p&gt;Single sign-out mechanism ensures that after user logs out from single application, other applications in SSO scope, which use the same user session, are notified. In response to notification, applications can drop the context of current user and redirect to login page, indicating current user is logged out and authentication is required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure AD B2C supported session management methods
&lt;/h2&gt;

&lt;p&gt;Below table shows which OpenId Session management solutions, described in &lt;a href="///_posts/2023-01-30-article3-openid-connect-session-management.md"&gt;previous article&lt;/a&gt;, are supported by Azure AD B2C:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Solution Type&lt;/th&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;th&gt;Azure AD B2C support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;polling based&lt;/td&gt;
&lt;td&gt;check_session_iframe endpoint&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;polling based&lt;/td&gt;
&lt;td&gt;/authorize endpoint polling&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logout based&lt;/td&gt;
&lt;td&gt;Front-channel logout&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logout based&lt;/td&gt;
&lt;td&gt;Back-channel logout&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  /authorize endpoint polling
&lt;/h2&gt;

&lt;p&gt;In order to determine user session status, application can cyclically call Azure AD B2C &lt;em&gt;/authorize&lt;/em&gt; endpoint. &lt;em&gt;/authorize&lt;/em&gt; endpoint is used to initialize OAuth flow. In the context authentication it is called with &lt;em&gt;scope=openid&lt;/em&gt; parameter. As the result of the flow &lt;em&gt;id_token&lt;/em&gt; is passed to the application.&lt;/p&gt;




&lt;p&gt;MSAL.js always initializes PKCE grant flow to obtain id_token.&lt;/p&gt;




&lt;p&gt;Once user authenticates with Azure AD B2C, session cookie is created in user’s browser. Azure AD B2C creates &lt;strong&gt;&lt;em&gt;x-ms-cpim-sso:{tenantId}&lt;/em&gt;&lt;/strong&gt; cookie with the value of user’s session id. Cookie is encrypted and can be interpreted solely by Azure AD B2C.&lt;/p&gt;

&lt;p&gt;If Azure AD B2C user session cookie exists in user’s browser, it is automatically attached to every call to its origin, including subsequent &lt;em&gt;/authorize&lt;/em&gt; endpoint calls. In case attached session cookie is valid (not expired, not malformed, not revoked) PKCE flow is completed, without user being asked to enter credentials.&lt;/p&gt;

&lt;p&gt;*Note that each flow, results in fresh id_token being passed to the application. Application is always updated with latest user data.&lt;/p&gt;

&lt;p&gt;To take advantage from &lt;em&gt;/authorize&lt;/em&gt; endpoint pooling, &lt;em&gt;prompt=none&lt;/em&gt; parameter should be included in &lt;em&gt;/authorize&lt;/em&gt; request. Otherwise, login form will always be displayed, regardless the active session.&lt;/p&gt;

&lt;p&gt;Following diagram present initial authentication flow. During initial authentication user is asked to enter credentials. Next, session cookie is settled in user’s browser. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gUTInynR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article4/azure-b2c-authorize-pooling.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gUTInynR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article4/azure-b2c-authorize-pooling.png" alt="authorize pooling first call" width="880" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After initial authentication, application can start polling &lt;em&gt;/authorize&lt;/em&gt; endpoint (diagram presents behavior, when there is a valid session cookie available in the user browser)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EvK2p8IR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article4/azure-b2c-authorize-pooling-subsequent-request.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EvK2p8IR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article4/azure-b2c-authorize-pooling-subsequent-request.png" alt="authorize pooling subsequent call" width="828" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  User Experience
&lt;/h3&gt;

&lt;p&gt;The common practice to improve user experience, is to embed &lt;em&gt;/authorize&lt;/em&gt; request in the iframe. Instead of redirecting the user to IdP site and back, iframe gives a feeling of a background call, which doesn’t interrupt user interaction with application. MSAL.js provides &lt;strong&gt;&lt;em&gt;ssoSilent&lt;/em&gt;&lt;/strong&gt; function to acquire &lt;em&gt;id_token&lt;/em&gt; using iframe.&lt;/p&gt;

&lt;p&gt;Below there is a fragment of MSAL.js core implementation related with &lt;em&gt;ssoSilent&lt;/em&gt; function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;ssoSilent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SsoSilentRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AuthenticationResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRequestCorrelationId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
   &lt;span class="c1"&gt;//... &lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;silentIframeClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createSilentIframeClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
 &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;silentIframeClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;acquireToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
 &lt;span class="c1"&gt;//... &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  React to session expiration
&lt;/h3&gt;

&lt;p&gt;So far, we focused on happy path, assuming there is a valid user session cookie available in the browser. Once there is no active user session provided to Azure AD B2C, &lt;em&gt;ssoSilent&lt;/em&gt; function call fails.&lt;/p&gt;

&lt;p&gt;In case session cookie is missing, expired or not valid, Azure AD B2C displays login form. In order to prevent clickjacking attack, Azure AD B2C doesn’t allow to display login form in iframe. It is indicated by X-Frame-Options: DENY http header, added by Azure AD B2C to login page. Clickjacking is very well described in &lt;a href="https://owasp.org/www-community/attacks/Clickjacking"&gt;OWASP&lt;/a&gt; and &lt;a href="https://auth0.com/blog/preventing-clickjacking-attacks/"&gt;Auth0&lt;/a&gt; articles.&lt;/p&gt;

&lt;p&gt;Attempt to display login form in iframe using &lt;em&gt;ssoSilent&lt;/em&gt; method ends with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;InteractionRequiredAuthError: interaction_required: AADB2C90077: 
User does not have an existing session and request prompt parameter has a value of 'None'. 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This exception can be used as an indication of user session expiration. Exception handling block can redirect user to custom logout page or force to reauthenticate by redirecting to &lt;em&gt;/authorize&lt;/em&gt; endpoint (without iframe). Following example shows how to logout user from application when session no longer valid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callSsoSilent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssoSilent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;silentRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;InteractionRequiredAuthError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logoutRedirect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
        &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getActiveAccount&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; 
        &lt;span class="c1"&gt;//prevents from notifying a server about logout, logout is performed only locally - in applicaiton scpoe &lt;/span&gt;
        &lt;span class="na"&gt;onRedirectNavigate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; 
      &lt;span class="p"&gt;});&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="c1"&gt;//handling of other error &lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt; 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Third party cookies
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;/autorize&lt;/em&gt; endpoint polling solution is dependent on third-party cookie access. Azure AD B2C, requested in iframe from application domain, needs to access session cookie. In this context, it is considered as third party cookies.&lt;/p&gt;

&lt;p&gt;For security reasons, some browsers implement strict policies related with third-party cookies access. To ensure access to session cookie is possible, Azure AD B2C tenant can be configure to be available in the same domain as application. For example &lt;code&gt;https://login.example.com&lt;/code&gt; while app is hosted on &lt;code&gt;https://example.com&lt;/code&gt;. Now, session cookie is not considered as third party, since it originates from the same domain. It can be achieved using &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-domain?pivots=b2c-custom-policy"&gt;Azure Front Door&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Azure AD B2C API rate limits
&lt;/h3&gt;

&lt;p&gt;It is important to consider increased network traffic and possible latency in polling implementation. Every polling-based strategy should consider API rate limits, to ensure they are not exceeded. Azure AD B2C limits requests in contexts of tenant and IP.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Maximum requests per IP per Azure AD B2C tenant&lt;/td&gt;
&lt;td&gt;6,000/5min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum requests per Azure AD B2C tenant&lt;/td&gt;
&lt;td&gt;200/sec&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  To be continued
&lt;/h2&gt;

&lt;p&gt;In next article I will focus on &lt;strong&gt;front-channel logout&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>misc</category>
      <category>azureadb2c</category>
      <category>oauth</category>
      <category>authentication</category>
    </item>
    <item>
      <title>OpenId Connect Session Management</title>
      <dc:creator>Michał Silski</dc:creator>
      <pubDate>Mon, 30 Jan 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/melmanm/openid-connect-session-management-4ach</link>
      <guid>https://dev.to/melmanm/openid-connect-session-management-4ach</guid>
      <description>&lt;p&gt;OpenId-Connect &lt;strong&gt;session&lt;/strong&gt; represents the authenticated user context, maintained between Applications and Identity Provider Server. Session enables applications to provide seamless authentication experience by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoiding frequent authentication&lt;/strong&gt; – As long as there is an active session available, application can acquire new access/id tokens from Identity Provider Server, without user being asked to re-authenticate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enabling Single sign-on (SSO)&lt;/strong&gt; - SSO enables multiple applications launched in the same web browser and working under the same Identity Provider Server to share the same user session. Once user logs-in into single application, other applications, can be used, without user being asked to re-authenticate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Synchronizing user context across applications&lt;/strong&gt; - From application perspective, it is important to know if it works in the context of logged-in user or not. Since identity management is delegated to Identity Provider Server, application needs to be synchronized with Identity Provider. Moreover, it needs to react on user session events. It becomes challenging, in SSO scenarios, where multiple applications, use the same user session. Some actions like user logout, password reset or users account deletion requires all other applications using current session to be informed.&lt;br&gt;&lt;br&gt;
Additionally, Identity Provider servers usually provides administrator option to revoke all user’s sessions. It can be used if user device was lost or stolen to protect against unauthorized access and malicious usage.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article I will describe how applications determine user session status, considering SSO scenarios. I will focus on methods provided by OpenId Connect. For applications requiring custom solutions, described methods can be used as a foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Terminology&lt;/li&gt;
&lt;li&gt;Determining session status&lt;/li&gt;
&lt;li&gt;
Polling Based Solutions

&lt;ul&gt;
&lt;li&gt;Solution 1 - check_session_iframe endpoint&lt;/li&gt;
&lt;li&gt;Solution 2 - /authorize endpoint polling&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Logout Based Solutions

&lt;ul&gt;
&lt;li&gt;Solution 3 - Front-Channel Logout&lt;/li&gt;
&lt;li&gt;Solution 4 - Back-Channel Logout&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Terminology
&lt;/h2&gt;

&lt;p&gt;To keep this article consistent, I will use following naming for OpenId Connect parties&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;RP&lt;/em&gt;&lt;/strong&gt; – Relaying Party, Client Application&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;IdP&lt;/em&gt;&lt;/strong&gt; - Authorization Server, Identity Provider Server&lt;/p&gt;

&lt;p&gt;additionally&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;SSO scope&lt;/em&gt;&lt;/strong&gt; – RP’s registered under common IdP, able to utilize the same user session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Determining session status
&lt;/h2&gt;

&lt;p&gt;Applications usually provide different functionalities depending if running in the context of logged-in user or not. Below solutions enables RPs to determine current session status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polling Based Solutions
&lt;/h2&gt;

&lt;p&gt;Polling, in general, is a way to determine a status of an asset, by actively, cyclically, checking its value. In the context of OpenId Connect and session management, polling solutions are based on cyclic requests to IdP to gather latest user session status.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution 1 - check_session_iframe endpoint
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;check_session_iframe&lt;/em&gt; endpoint is described in &lt;a href="https://openid.net/specs/openid-connect-session-1_0.html"&gt;OpenId Connect Session-related specs&lt;/a&gt;. It requires RP to load JavaScript code from IdP. Loaded code validates current session state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G2Dq9riP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article3/session_with_iframe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G2Dq9riP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article3/session_with_iframe.png" alt="check_session_iframe" width="838" height="948"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  session_state
&lt;/h4&gt;

&lt;p&gt;In response to initial &lt;em&gt;/authorize&lt;/em&gt; endpoint request, IdP returns &lt;em&gt;session_state&lt;/em&gt; parameter. Additionally, after successful authentication, IdP stores a session cookie in user’s browser.&lt;/p&gt;

&lt;h4&gt;
  
  
  loading IdP code
&lt;/h4&gt;

&lt;p&gt;RP application, launched in the browser, should include an iframe element that loads content from IdP’s &lt;em&gt;/check_session_iframe&lt;/em&gt; endpoint. Loaded content is a JavaScript function, which validates if user session is active or not. Validation is based on &lt;em&gt;session_state&lt;/em&gt; and &lt;em&gt;clientId&lt;/em&gt; provided by RP as arguments. Function calculates expected session cookie value and compares it with actual session cookie, stored in the browser. Once expected and actual session cookies are equal, function returns unchanged (which indicates active session), or changed. This logic can be expressed by following condition:&lt;code&gt;f(session_state, client_id) == session_cookie ? unchanged : changed&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Cyclic execution
&lt;/h4&gt;

&lt;p&gt;IdP validation function can be executed periodically by RP, to achieve polling mechanism. OpenId Connect documentation recommends to load anther iframe directly from RP, that will be solely responsible for cyclically triggering validation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Network traffic
&lt;/h4&gt;

&lt;p&gt;Network traffic needed to check session status is very limited. Once IdP iframe is loaded, session can be validated using loaded JavaScript code only - with no IdP interaction. That can be considered as main advantage of check_session_iframe-based solution.&lt;/p&gt;

&lt;h4&gt;
  
  
  Limitation: Iframe and 3rd Party Cookies
&lt;/h4&gt;

&lt;p&gt;Iframe based has a serious limitation in modern, security-oriented browsers. OpenId Connect documentation stands:&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Note that at the time of this writing, some User Agents (browsers) are starting to block access to third-party content by default to block some mechanisms used to track the End-User’s activity across sites. Specifically, the third-party content being blocked is website content with an origin different that the origin of the focused User Agent window. Site data includes cookies and any web storage APIs (sessionStorage, localStorage, etc.).&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;For security reasons more and more browsers don’t allow cross domain cookies access. Since RP loads IdP iframe, it requires an access to the session cookie, which belongs to IdP.&lt;/p&gt;

&lt;p&gt;Following list presents popular browser support for 3rd party cookies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Safari – not supported&lt;/li&gt;
&lt;li&gt;Google Chrome– planning to finish support in 2024&lt;/li&gt;
&lt;li&gt;Firefox – third party cookies are blocked by default&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some IdPs allows to configure a custom domain, common for RP and IdP. Having IdP and RP in the same domain makes cookie access possible, regardless 3-rd party cookies restrictions. For instance, IdP can be accessed at &lt;code&gt;https://login.example.com&lt;/code&gt; while app is hosted on &lt;code&gt;https://example.com&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution 2 - /authorize endpoint polling
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest"&gt;OpenId Connect specification&lt;/a&gt; describes &lt;em&gt;/authorize&lt;/em&gt; endpoint. It is used to initiate authentication flow. &lt;em&gt;/authorize&lt;/em&gt; endpoint accepts &lt;em&gt;prompt&lt;/em&gt; parameter. In case &lt;em&gt;prompt&lt;/em&gt; parameter set to &lt;em&gt;none&lt;/em&gt;, IdP checks session cookie, provided within the request. If attached cookie represents an active session, user will not be asked to enter credentials. If provided session cookie is invalid or associated user session is no longer active, user will be asked to enter login credentials.&lt;/p&gt;

&lt;p&gt;In the context of authentication &lt;em&gt;/authorize&lt;/em&gt; endpoint call initiates grant flow, which eventually returns &lt;em&gt;id_token&lt;/em&gt; to the application. By calling &lt;em&gt;/authorize&lt;/em&gt; endpoint cyclically, application is updated with latest user-related claims, stored in id_token. It can be an advantage, when other application modifies user claims in the meantime.&lt;/p&gt;

&lt;p&gt;Some IdPs providers, supports &lt;em&gt;rolling session&lt;/em&gt;. Session expiration can be postponed every time, when corresponding session cookie is used to acquire new id_token. It allows to keep the session alive longer, when it is actively used by RP.&lt;/p&gt;

&lt;h4&gt;
  
  
  Iframe for seamless experience
&lt;/h4&gt;

&lt;p&gt;To provide seamless user experience &lt;em&gt;/authorize&lt;/em&gt; endpoint can be called using iframe. Thanks to iframe, user is not redirected to /authorize page and back on every call. Instead, call can be done “in the background”- without users notice.&lt;/p&gt;

&lt;h4&gt;
  
  
  Limitation 1: Iframe and 3rd Party Cookies
&lt;/h4&gt;

&lt;p&gt;In order to validate user session IdP requires an access to session cookie stored in browser. Considering strict 3-rd party policy, it might not be possible, when IdP was called from other domain’s iframe.&lt;/p&gt;

&lt;p&gt;Some IdPs allows to configure a custom domain, common for RP and IdP. Having IdP and RP in the same domain makes cookie access possible, regardless 3-rd party cookies restrictions. For instance, IdP can be accessed at &lt;code&gt;https://login.example.com&lt;/code&gt; while app is hosted on &lt;code&gt;https://example.com&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Limitation 2: Iframe and Clickjacking protection
&lt;/h4&gt;

&lt;p&gt;Iframe usage leads to some more security concerns than just 3rd party cookies, especially in the context of authentication and authorization. Iframe element can be used to perform clickjacking attack. Clickjacking is very well described in &lt;a href="https://owasp.org/www-community/attacks/Clickjacking"&gt;OWASP&lt;/a&gt; and &lt;a href="https://auth0.com/blog/preventing-clickjacking-attacks/"&gt;Auth0&lt;/a&gt; articles. To prevent the attack, a &lt;strong&gt;&lt;em&gt;X-Frame-Options: DENY&lt;/em&gt;&lt;/strong&gt; header can be added to http header, to make sure, browser will not allow to load th login page inside iframe.&lt;/p&gt;

&lt;p&gt;As long as there is active user session, &lt;em&gt;/authorize&lt;/em&gt; endpoint can be successfully called from iframe. Once user session expires, IdP tries to redirect next &lt;em&gt;/authorize&lt;/em&gt; call to the login page. Since login page likely includes &lt;em&gt;X-Frame-Options: DENY&lt;/em&gt; header and it is called from iframe, redirect will fail.&lt;/p&gt;

&lt;p&gt;As a workaround some IdP providers recommend following solution expressed in pseudo-code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nf"&gt;CallAutorizeUsingIFrame&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="k"&gt;catch&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nf"&gt;CallAuthorizeUsingRedirect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In example above &lt;em&gt;/authorize&lt;/em&gt; endpoint is called using redirect, only when iframe call fails. Assuming failure is caused by expired session and attempt to display login page in iframe, user will be redirected to login page outside the iframe in catch block. After successful login new session is established and &lt;em&gt;/authorize&lt;/em&gt; can be called from iframe again.&lt;/p&gt;

&lt;p&gt;Besides that, some IdPs allows to configure &lt;em&gt;X-Frame-Options&lt;/em&gt; header value if needed, however it should be considered as “unsafe mode”.&lt;/p&gt;

&lt;h4&gt;
  
  
  Limitation 3: API Rate Limits
&lt;/h4&gt;

&lt;p&gt;/authorize endpoint polling should be considered in the context of IdP API rate limits. If polling interval is too short, API call limit can be reached.&lt;/p&gt;




&lt;h2&gt;
  
  
  Logout Based Solutions
&lt;/h2&gt;

&lt;p&gt;RPs use logout-based solutions to track active user session by listening on logout event. RP provides an endpoint that can be called by IdP, in case logout event occurred in other application, which uses the same user session.&lt;/p&gt;

&lt;p&gt;RP logout is initiated by calling IdP’s &lt;a href="https://openid.net/specs/openid-connect-rpinitiated-1_0.html#rfc.section.2"&gt;&lt;em&gt;/logout&lt;/em&gt; endpoint&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;IdP clears session cookie on logout request. Moreover, IdP can spread logout information to other RPs using&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Front-Channel Logout&lt;/li&gt;
&lt;li&gt;Back-Channel Logout&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Solution 3 - Front-Channel Logout
&lt;/h3&gt;

&lt;p&gt;Front-Channel Logout is described in &lt;a href="https://openid.net/specs/openid-connect-frontchannel-1_0.html"&gt;OpenId Connect documentation&lt;/a&gt;. Front-Channel Logout procedure is initiated by calling IdP’s &lt;a href="https://openid.net/specs/openid-connect-rpinitiated-1_0.html#rfc.section.2"&gt;&lt;em&gt;/logout&lt;/em&gt; endpoint&lt;/a&gt;. IdP tracks RPs and their active sessions. Based on session cookie, provided within /logout request IdP finds all applications using corresponding session. Once IdP receives logout request from RP, logout event is spread to other RPs (which uses the same session), by calling their &lt;em&gt;frontchannel_logout_uri&lt;/em&gt; endpoints via front channel. (All RPs need to be configured with &lt;em&gt;frontchannel_logout_uri&lt;/em&gt; in IdP client registration properties).&lt;/p&gt;

&lt;p&gt;RPs can adjust their internal state and clean information associated with logged-in user, after receiving front-channel logout request.&lt;/p&gt;

&lt;p&gt;Front-channel logout mechanism utilizes iframes. IdP loads a website containing iframes in response to &lt;em&gt;/logout&lt;/em&gt; request. Each iframe &lt;em&gt;src&lt;/em&gt; corresponds with single &lt;em&gt;frontchannel_logout_uri&lt;/em&gt;. Once all RPs are notified IdP redirects user to &lt;em&gt;post_logout_redirect_uri&lt;/em&gt; specified in initial &lt;em&gt;/logout&lt;/em&gt; request parameters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QLC6WoSn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article3/azure-b2c-front-channel-logout.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QLC6WoSn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article3/azure-b2c-front-channel-logout.png" alt="front-channel logout" width="880" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Limitation 1: Iframe and 3rd Party Cookies
&lt;/h4&gt;

&lt;p&gt;Main limitation of frontend logout solutions is, again, related with third-party cookies. Since &lt;em&gt;frontchannel_logout_uri&lt;/em&gt; is iframed and called on IdP, there might be some limitations with access to RP’s cookies.&lt;/p&gt;

&lt;h4&gt;
  
  
  Limitation 2: URI fragment component
&lt;/h4&gt;

&lt;p&gt;Additionally, &lt;em&gt;frontchannel_logout_uri&lt;/em&gt; must not contains fragment component (like example.com#logout). Many SPA frameworks uses fragment components for routing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution 4 - Back-Channel Logout
&lt;/h3&gt;

&lt;p&gt;Back-Channel Logout is described in &lt;a href="https://openid.net/specs/openid-connect-backchannel-1_0.html"&gt;OpenId Connect documentation&lt;/a&gt;. Logout information is sent to all RPs using current browser session via back-channel. In response to &lt;em&gt;/logout&lt;/em&gt; request IdP creates a &lt;a href="https://openid.net/specs/openid-connect-backchannel-1_0.html#LogoutToken"&gt;&lt;em&gt;logout_token&lt;/em&gt;&lt;/a&gt; and sends it via back-channel POST call to RP’s &lt;em&gt;/backchannel_logout_uri&lt;/em&gt;. &lt;em&gt;backchannel_logout_uri&lt;/em&gt; is associated with RP in IdP client registration properties.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;logout_token&lt;/em&gt; should be validated by RP. It is critical to compare &lt;em&gt;logout_token&lt;/em&gt; properties (like &lt;em&gt;sub&lt;/em&gt;) with current &lt;em&gt;id_token&lt;/em&gt; and verify if logout request corresponds with currently logged in user.&lt;/p&gt;

&lt;p&gt;Once all RPs are notified IdP redirects user to &lt;em&gt;post_logout_redirect_uri&lt;/em&gt; specified in initial &lt;em&gt;/logout&lt;/em&gt; request parameters.&lt;/p&gt;

&lt;p&gt;Not all IdPs supports Back-Channel logout. If Back-Channel is supported, &lt;em&gt;backchannel_logout_supported&lt;/em&gt; property is visible in IdP discovery endpoint.&lt;/p&gt;

</description>
      <category>openidconnect</category>
      <category>oauth20</category>
      <category>oauth</category>
    </item>
    <item>
      <title>OAuth 2.0 refresh tokens with Azure AD B2C</title>
      <dc:creator>Michał Silski</dc:creator>
      <pubDate>Sun, 29 Jan 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/melmanm/oauth-20-refresh-tokens-with-azure-ad-b2c-21i2</link>
      <guid>https://dev.to/melmanm/oauth-20-refresh-tokens-with-azure-ad-b2c-21i2</guid>
      <description>&lt;p&gt;Refresh tokens are commonly used in OAuth based authorization scenarios. The purpose of refresh token is to retrieve new id/access token from authorization server, without user interaction. In simple scenarios, once access token expires, user is forced to reauthenticate in order to get new token. With refresh tokens, expired access token can be replaced with fresh one in the background, without user interaction.&lt;/p&gt;

&lt;p&gt;Using refresh tokens improves application security. One might think it would be just enough to extend an expiration time of id/access token. By making it long lived, user avoids often re-authentication. This approach may put your application at greater risk of token being intercepted and used by attacker.&lt;/p&gt;

&lt;p&gt;Once id/access token is retrieved from Auth server, it can be used until it expires against resource server (e.g. personal calendar service). Risk of token interception and malicious usage increases as the same long-lived id/access token is used over and over again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs9fwaiupxyf6lrzur7i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs9fwaiupxyf6lrzur7i.png" alt="wrong way" width="527" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With short-lived access token, risk of its interception still exists, however their malicious usage can be minimized as they shortly expire and cannot be used against resource server anymore. In Azure AD B2C default access token lifetime is 60 minutes and can be configured in a range of 5 minutes to 24 hours.&lt;/p&gt;

&lt;p&gt;Besides external risks long-lived access tokens, expose resources to internal risks. Once user access to specific resource is changed or revoked, it will be reflected only in access token generated after this change. With long lived access tokens user might keep the access to the resource for long time, even if it was changed or revoked in the meantime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Refreshing tokens by the book&lt;/li&gt;
&lt;li&gt;
Refresh tokens settings in Azure AD B2C

&lt;ul&gt;
&lt;li&gt;User Flows&lt;/li&gt;
&lt;li&gt;Custom Policies&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Refresh token revocation

&lt;ul&gt;
&lt;li&gt;Revocation and access tokens&lt;/li&gt;
&lt;li&gt;Azure AD B2C revocation scenarios&lt;/li&gt;
&lt;li&gt;One time use refresh token&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Customizing refresh token flow&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Refreshing tokens by the book
&lt;/h2&gt;

&lt;p&gt;Below diagram presents how refresh token can be used in Authorization Code Grant flow. &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xpm4m5mfocln6rt40cy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xpm4m5mfocln6rt40cy.png" alt="refresh token" width="724" height="709"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to get refresh token from Azure AD B2C authorization server following request needs to be sent to /token endpoint&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST https://{tenant_id}.b2clogin.com/{tenant_id}.onmicrosoft.com/{policy}oauth2/v2.0/token 
?grant_type=refresh_token 
&amp;amp;client_secret={client_secret} 
&amp;amp;client_id={client_id} 
&amp;amp;scope=offline_access //in addition, openid or specific access can be specified 
&amp;amp;refresh_token={refresh_token} 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Auth server response includes new refresh token plus access or id token (depending of specified request scope).&lt;/p&gt;




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

&lt;p&gt;&lt;em&gt;Authorization code grant flow is presented as example. Note that refresh token can be used in OAuth flows other than that, except implicit grant flow.&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;em&gt;Refresh token is encrypted during generation, and decrypted by Auth server once it is used. Its payload is transparent for client application and can be understood solely by Auth server.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Refresh tokens settings in Azure AD B2C
&lt;/h2&gt;

&lt;p&gt;Azure AD B2C governs refresh tokens and controls their behavior. Refresh token can be configured using 3 properties&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;refresh_token_lifetime_secs&lt;/em&gt;&lt;/strong&gt; – describes how long single refresh token is valid. Once refresh token lifetime expires, it cannot be used to gather new refresh token and will be refused by Auth server. Minimum value is 24h, maximum is 90 days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;rolling_refresh_token_lifetime_secs&lt;/em&gt;&lt;/strong&gt; – describes time frame in which token refreshing can be continued. Refresh token can be used to get new refresh token. New refresh token can be used to get another refresh token etc. It creates a chain. This property specifies maximum length of refresh token chain. Minimum value is 24h, maximum is 365 days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;allow_infinite_rolling_refresh_token&lt;/em&gt;&lt;/strong&gt; – once this property is set to true, refresh token chain is not time limited.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These properties can be configured for both User Flows and Custom Policies (Identity Experience Framework).&lt;/p&gt;

&lt;h4&gt;
  
  
  User Flows
&lt;/h4&gt;

&lt;p&gt;User Flows allows to configure refresh token properties under Settings/Token Lifetime &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuixlybhcmyk13o4bq4le.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuixlybhcmyk13o4bq4le.jpg" alt="refresh token" width="447" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Custom Policies
&lt;/h4&gt;

&lt;p&gt;Custom Policies takes advantage of extending OpenId Connect &lt;em&gt;TechnicalProfile&lt;/em&gt;. Azure AD B2C custom policies &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#custom-policy-starter-pack" rel="noopener noreferrer"&gt;starter pack&lt;/a&gt; implements &lt;em&gt;TrustFrameworkBase&lt;/em&gt; custom policy, which defines &lt;em&gt;JwtIssuer&lt;/em&gt; - OpenId Connect &lt;em&gt;TechnicalProfile&lt;/em&gt;. It can be inherited and extended with refresh token settings like below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ClaimsProvider&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DisplayName&amp;gt;&lt;/span&gt;Token Issuer&lt;span class="nt"&gt;&amp;lt;/DisplayName&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TechnicalProfiles&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;TechnicalProfile&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"JwtIssuer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Metadata&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- 86400s = 24h --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;Item&lt;/span&gt; &lt;span class="na"&gt;Key=&lt;/span&gt;&lt;span class="s"&gt;"refresh_token_lifetime_secs"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;86400&lt;span class="nt"&gt;&amp;lt;/Item&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- 172800s = 48h --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;Item&lt;/span&gt; &lt;span class="na"&gt;Key=&lt;/span&gt;&lt;span class="s"&gt;"rolling_refresh_token_lifetime_secs"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;172800&lt;span class="nt"&gt;&amp;lt;/Item&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;Item&lt;/span&gt; &lt;span class="na"&gt;Key=&lt;/span&gt;&lt;span class="s"&gt;"allow_infinite_rolling_refresh_token"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/Item&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/Metadata&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/TechnicalProfile&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/TechnicalProfiles&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ClaimsProvider&amp;gt;&lt;/span&gt; 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refresh token lifetime configuration is reflected in response from /token endpoint. Response includes &lt;em&gt;refresh_token_expires_in&lt;/em&gt; property that corresponds with &lt;em&gt;refresh_token_lifetime_secs&lt;/em&gt; setting. Thanks to that application can request new refresh token before current expires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refresh token revocation
&lt;/h2&gt;

&lt;p&gt;Azure AD B2C does not provide OAuth &lt;em&gt;/revocation&lt;/em&gt; endpoint which is normally used to inform the Auth server specific token should not be accepted anymore.&lt;/p&gt;

&lt;p&gt;Token revocation can be achieved in three different ways using&lt;/p&gt;

&lt;h5&gt;
  
  
  1. Using Powershell
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Revoke-AzureADUserAllRefreshToken -ObjectId &amp;lt;String&amp;gt; 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  2. Using Graph API
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST https://graph.windows.net/myorganization/users/{user_id}/invalidateAllRefreshTokens?api-version 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  3. Using Azure portal
&lt;/h5&gt;

&lt;p&gt;&lt;em&gt;Revoke session&lt;/em&gt; button is available in B2C tenant user settings  &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cqnmwywv69rifv8g03d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cqnmwywv69rifv8g03d.jpg" alt="refresh token" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;This solution doesn’t invalidate refresh token directly. It is designed to revoke user session, not refresh tokens. Though it is possible to implement a custom logic which stops refresh token from being used, after user session is revoked. Specific implementation is described in customizing refresh token flow section.&lt;/strong&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  Azure AD B2C revocation scenarios
&lt;/h4&gt;

&lt;p&gt;Azure AD B2C token revocation possibilities seem to be designed for administrator usage scenarios. For example, when user loss device, administrator can revoke tokens, so reauthentication will be required.&lt;/p&gt;

&lt;p&gt;This approach doesn’t work well with scenarios, where users need to revoke their own tokens. However, it is valid requirement considering security reasons. After user logs out from application, refresh tokens are usually removed from application memory but they are not invalidated on Auth Server and still can be used. It creates a risk of malicious usage of intercepted refresh tokens.&lt;/p&gt;

&lt;h4&gt;
  
  
  Revocation and access tokens
&lt;/h4&gt;

&lt;p&gt;Refresh token revocation does not invalidate the access token. User does not lose the access immediately. Access tokens are valid until they expire. Once application gets an access token, there is no way to prevent application from accessing resource with this access token. Refresh token revocation prevents from acquiring new id/access tokens without reauthentication.&lt;/p&gt;

&lt;h4&gt;
  
  
  One time use refresh token
&lt;/h4&gt;

&lt;p&gt;Staying in the context of security, there are &lt;a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics" rel="noopener noreferrer"&gt;recommendation&lt;/a&gt; for Auth servers to generate one time use refresh tokens. Refresh token should be used only once to request new access and refresh tokens. New refresh token, can be used only once as well. This approach mitigates refresh token usage risk, even if it is intercepted by attacker. Though Azure AD B2C by default allows to use the same refresh token multiple times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizing refresh token flow
&lt;/h2&gt;

&lt;p&gt;Customization of token claims can be implemented using Azure AD B2C custom policies. Claims generation process can be customized, when the tokens are requested specifically using refresh token.&lt;/p&gt;

&lt;p&gt;This functionality gives variety of possibilities, for example&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refresh user claims in returned id token. Some claims might have changed after they were initially generated during sign in process.&lt;/li&gt;
&lt;li&gt;Call external API to check if user, who is trying to refresh the token, is on some blacklist. Interrupt token generation if that is the case.&lt;/li&gt;
&lt;li&gt;Check if user’s refresh token was revoked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example below, presents how to check if user session was revoked, before refreshing the token.&lt;/p&gt;

&lt;p&gt;After &lt;em&gt;/token&lt;/em&gt; endpoint is requested with with &lt;em&gt;grant_type=refresh_token&lt;/em&gt;, Azure AD B2C triggers &lt;em&gt;UserJourney&lt;/em&gt;, referenced in &lt;em&gt;RelyingParty&lt;/em&gt; as an Endpoint with &lt;em&gt;Id=Token&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;RelyingParty&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DefaultUserJourney&lt;/span&gt; &lt;span class="na"&gt;ReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"SignUpOrSignIn"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Endpoints&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Endpoint&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Token"&lt;/span&gt; &lt;span class="na"&gt;UserJourneyReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"RedeemRefreshToken"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Endpoints&amp;gt;&lt;/span&gt; 
... 
&lt;span class="nt"&gt;&amp;lt;/RelyingParty&amp;gt;&lt;/span&gt; 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;RedeemRefreshToken&lt;/em&gt; Journey is implemented in Azure AD B2C &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#custom-policy-starter-pack" rel="noopener noreferrer"&gt;starter pack&lt;/a&gt; in &lt;em&gt;TrustFrameworkBase.xml&lt;/em&gt; policy file. (Starter pack is recommended by Azure as a foundation for Custom Policies) Of course, you can extend, customize or use different &lt;em&gt;UserJourney&lt;/em&gt; instead.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;RedeemRefreshToken&lt;/em&gt; UserJourney includes three steps,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;UserJourney&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"RedeemRefreshToken"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PreserveOriginalAssertion&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/PreserveOriginalAssertion&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OrchestrationSteps&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OrchestrationStep&lt;/span&gt; &lt;span class="na"&gt;Order=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;Type=&lt;/span&gt;&lt;span class="s"&gt;"ClaimsExchange"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ClaimsExchanges&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;ClaimsExchange&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"RefreshTokenSetupExchange"&lt;/span&gt; &lt;span class="na"&gt;TechnicalProfileReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"RefreshTokenReadAndSetup"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/ClaimsExchanges&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/OrchestrationStep&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Extra steps can be added before or after this step for REST API or claims transformation calls--&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OrchestrationStep&lt;/span&gt; &lt;span class="na"&gt;Order=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;Type=&lt;/span&gt;&lt;span class="s"&gt;"ClaimsExchange"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ClaimsExchanges&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;ClaimsExchange&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"CheckRefreshTokenDateFromAadExchange"&lt;/span&gt; &lt;span class="na"&gt;TechnicalProfileReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"AAD-UserReadUsingObjectId-CheckRefreshTokenDate"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/ClaimsExchanges&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/OrchestrationStep&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OrchestrationStep&lt;/span&gt; &lt;span class="na"&gt;Order=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;Type=&lt;/span&gt;&lt;span class="s"&gt;"SendClaims"&lt;/span&gt; &lt;span class="na"&gt;CpimIssuerTechnicalProfileReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"JwtIssuer"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/OrchestrationSteps&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/UserJourney&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which utilize following &lt;em&gt;TechnicalProfiles&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;RefreshTokenReadAndSetup&lt;/em&gt;&lt;/strong&gt; – it just returns refresh token parameters like &lt;em&gt;objectId&lt;/em&gt; and &lt;em&gt;refreshTokenIssuedOnDateTime&lt;/em&gt; in output claims. Before executing refresh token UserJourney, refresh token claims seems to be already preloaded into claims bag. (It is different approach comparing to &lt;a href="https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/active-directory-b2c/userinfo-endpoint.md" rel="noopener noreferrer"&gt;UserInfoEndpoint implementation&lt;/a&gt;, where dedicated technical profile loads access token claims into claims bag explicitly).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;AAD-UserReadUsingObjectId-CheckRefreshTokenDate&lt;/em&gt;&lt;/strong&gt; – retrieves user information stored in azure AD. Since it uses base &lt;em&gt;AAD-UserReadUsingObjectId&lt;/em&gt; TechnicalProfile, basic user data will updated in returned id/access token. Additionally, &lt;em&gt;refreshTokensValidFromDateTime&lt;/em&gt; property is read from AD. When user’s refresh token is revoked &lt;em&gt;refreshTokensValidFromDateTime&lt;/em&gt; is set to time it happened.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;TechnicalProfile&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"AAD-UserReadUsingObjectId-CheckRefreshTokenDate"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputClaims&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OutputClaim&lt;/span&gt; &lt;span class="na"&gt;ClaimTypeReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"refreshTokensValidFromDateTime"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OutputClaim&lt;/span&gt; &lt;span class="na"&gt;ClaimTypeReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"displayName"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/OutputClaims&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputClaimsTransformations&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OutputClaimsTransformation&lt;/span&gt; &lt;span class="na"&gt;ReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"AssertRefreshTokenIssuedLaterThanValidFromDate"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/OutputClaimsTransformations&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;IncludeTechnicalProfile&lt;/span&gt; &lt;span class="na"&gt;ReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"AAD-UserReadUsingObjectId"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/TechnicalProfile&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;By comparing &lt;em&gt;refreshTokensValidFromDateTime&lt;/em&gt; property with &lt;em&gt;refreshTokenIssuedOnDateTime&lt;/em&gt; it is possible to validate if used refresh token should be accepted by auth server. This logic is implemented by output claim transformation. Once &lt;em&gt;refreshTokensValidFromDateTime&lt;/em&gt; is greater that &lt;em&gt;refreshTokenIssuedOnDateTime&lt;/em&gt;, exception is thrown and UserJourney is interrupted. In that case, Auth server returns &lt;strong&gt;400-Bad request&lt;/strong&gt; http response, including body:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invalid_grant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="nl"&gt;"error_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AADB2C90129: The provided grant has been revoked. Please reauthenticate and try again.Correlation ID: xxxxxx-xxxx-xxxx-xxxx-xxxxxxxx&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;Timestamp: xxxx-xx-xx xx:xx:xxx&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;JwtIssuer&lt;/em&gt;&lt;/strong&gt; - OpenIdConnect technical profile that issues requested token and returns it to relaying party. Refresh token properties described above, in Refresh tokens properties in Azure AD B2C section, can be specified in its definition.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>misc</category>
      <category>azureadb2c</category>
      <category>openidconnect</category>
      <category>azure</category>
    </item>
    <item>
      <title>Progressive profiling with Azure AD B2C</title>
      <dc:creator>Michał Silski</dc:creator>
      <pubDate>Sat, 28 Jan 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/melmanm/progressive-profiling-with-azure-ad-b2c-49ah</link>
      <guid>https://dev.to/melmanm/progressive-profiling-with-azure-ad-b2c-49ah</guid>
      <description>&lt;p&gt;Azure AD B2C provides advanced tools for user identity and access management. Modern applications often delegate identity and access management to external services. At the same time authorization and authentication needs to be highly flexible, providing features as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integration with external identity providers like GitHub or Facebook etc.&lt;/li&gt;
&lt;li&gt;Customization of user experience during sign-in, sign-up, profile edit etc.&lt;/li&gt;
&lt;li&gt;Extension of user identity with application-specific information&lt;/li&gt;
&lt;li&gt;Performing calls to external APIs during authorization or authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Azure AD B2C supports all these scenarios and even more. In this article I will focus on progressive profiling implementation, taking advantage of Azure AD B2C support for highly customized user experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Progressive Profiling&lt;/strong&gt; is a gradual way of collecting user preferences. As application collects user’s preferences it can provide more personalized, better suited experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The concept of Progressive Profiling&lt;/li&gt;
&lt;li&gt;Progressive Profiling implementation using Azure AD B2C&lt;/li&gt;
&lt;li&gt;Progressive Profiling UserJourney - Custom Policy implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The concept of Progressive Profiling
&lt;/h2&gt;

&lt;p&gt;Imagine user is singing-up for gym website. User is asked to fill in standard sign-up form, including email and username. Gym website added personalized training offer recently. In result, during sign-up process, user is additionally asked about favorite exercises, training preferences, age, calories eaten per day, health condition and past training record. At this point most of potential customers would probably resign from signing-up. The main reason is most likely users don’t trust the website yet to share such detailed information.&lt;/p&gt;

&lt;p&gt;Progressive profiling is a way to ask these questions gradually, as trust to the website (and company) increases and user observes benefits of personalization. Let’s assume user signs-up with minimum information required. After several visits, during next sign-in process, user is informed about personalized training offer and asked to choose favorite exercise from the list. This question can be not answered, however after few visits on the website and maybe on the gym itself, there is a better chance this information will be shared. As users observe benefits form personalization, they become more eager to share their preferences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Progressive Profiling implementation using Azure AD B2C
&lt;/h2&gt;

&lt;p&gt;Azure AD B2C allows to incorporate progressive profiling into sign-in process. It is possible to customize sign-in process and extend it with gathering and processing additional user’s input. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o_nzstjn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/azure-b2c-progressive-profiling-diagram-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o_nzstjn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/azure-b2c-progressive-profiling-diagram-1.png" alt="Azure AD B2C login 1" width="880" height="381"&gt;&lt;/a&gt; &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q2mCJBlK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/azure-b2c-progressive-profiling-diagram-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q2mCJBlK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/azure-b2c-progressive-profiling-diagram-2.png" alt="Azure AD B2C login 2" width="880" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Provided information can be stored in Azure AD B2C tenant and served within id_token. Having preferences as a part of user identity enables application to personalize services it provides.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;id_token&lt;/em&gt; token can be extended with progressive profiling information as follows: &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RyxrV2C0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/azure-b2c-open-id-token.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RyxrV2C0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/azure-b2c-open-id-token.jpg" alt="Azure AD B2C login 2" width="722" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Azure AD B2C provides two ways of configuring identity related flows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User flows&lt;/strong&gt; - predefined, build-in policies designed to handle most common scenarios; can be configured within Azure Portal UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity Experience Framework (Custom Policies)&lt;/strong&gt; - Configuration of identity-related processes like sign-in, sign-up, reset password or profile edit is called policy. Policies are defined in xml files. Xml-based policies are flexible and can be highly customized.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ANLnnKDH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/azure-b2c-policies.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ANLnnKDH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/azure-b2c-policies.jpg" alt="Azure AD B2C login 2" width="259" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Progressive profiling scenario is not supported by user flows and needs to be implemented using Identity Experience Framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TrustFrameworkPolicy&lt;/strong&gt; is top-level xml element of policy file. It defines claims schema, claims transformation, token generation steps and more. It is well described on &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory-b2c/trustframeworkpolicy"&gt;https://learn.microsoft.com/en-us/azure/active-directory-b2c/trustframeworkpolicy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;TrustFrameworkPolicy uses concept of UserJourneys. &lt;strong&gt;UserJourney&lt;/strong&gt; includes steps (Orchestration Steps), needs to be taken to perform identity related actions like sign-in or sign-up. In the context of signing-in, the result of UserJourney is generated id_token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OrchestrationSteps&lt;/strong&gt; process claims using TechnicalProfiles. &lt;strong&gt;TechnicalProfile&lt;/strong&gt; generates output claims based on input claims by interacting with AD, calling external API, gathering data from user, etc. TechnicalProfiles are connected together in a chain by OrchestrationSteps, creating UserJourney.&lt;/p&gt;

&lt;p&gt;UserJourney starts when OAuth 2.0 flow is initiated (in most cases by calling corresponding /authorize endpoint).&lt;/p&gt;

&lt;p&gt;Result &lt;em&gt;id_token&lt;/em&gt; is passed to &lt;em&gt;RedirectUrl&lt;/em&gt; once application requests a token. RedirectedUrl can be configured in Azure AD B2C tenant for registered application.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Using implicit grant flow /authorize endpoint call, which initiates authentication, is considered as the token request at the same time&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Progressive Profiling UserJourney - Custom Policy implementation
&lt;/h2&gt;

&lt;p&gt;The concept solution of progressive profiling policy is available on my &lt;a href="https://github.com/melmanm/azure-b2c-custom-policy-progressive-profiling"&gt;GitHub&lt;/a&gt;. Repository contains following files implementing policies&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TrustFrameworkBase.xml (B2C_1A_TrustFrameworkBase policy)&lt;/li&gt;
&lt;li&gt;TrustFrameworkLocalization.xml (B2C_1A_TrustFrameworkLocalization policy)&lt;/li&gt;
&lt;li&gt;TrustFrameworkExtensions.xml (B2C_1A_TrustFrameworkExtensions policy)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These files originate from &lt;a href="https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack"&gt;Azure custom policy samples repository&lt;/a&gt;. They are recommended as a foundation for building custom policies. Additionally, my repository contains xml files, which implements custom progressive profiling policy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ProgressiveProfileTrustFrameworkBase.xml&lt;/strong&gt; (B2C_1A_ ProgressiveProfile_TrustFrameworkBase policy) defines base claims and technical profile to handle progressive profiling, exchange claims with AAD and handle user input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ProgressiveProfileTrustFrameworkExtensions.xml&lt;/strong&gt; (B2C_1A_ ProgressiveProfile_TrustFrameworkExtensions policy) defines application-specific claims which will be gathered from user in progressive profiling flow. It implements UserJourney.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ProgressiveProfileSignUpOrSignin.xml&lt;/strong&gt; (B2C_1A_ProgressiveProfile_SignUpOrSignIn policy) defines RelyingParty which returns final token claims.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Policies can be inherited. Inheritance allows to use or extend elements defined in parent policies. Following diagram presents inheritance of policies in this implementation: &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PTVLjE7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/b2c-progressive-profiling-policies-inheritance.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PTVLjE7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/b2c-progressive-profiling-policies-inheritance.png" alt="Azure AD B2C login 2" width="501" height="591"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below diagram shows the concept of UserJourney that incorporates progressive profiling into user sign-in flow.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Presented UserJourney uses several custom claims, to control progressive profiling UserJourney&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PPCounter&lt;/strong&gt; – used to count user sign-in actions. Once counter reaches specific value, progressive profiling form should be displayed to the user after signing-in. Next PPCounter is reset. It is saved in AD user attributes, so value is preserved between UserJourney executions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PPShouldExecute&lt;/strong&gt; – determines if PPCounter reaches specific level after which progressive profiling form should be displayed to the user. In given example PPShouldExecute is set once PPCounter equals 3. It means user is asked to fill progressive profiling form after every 3rd sign-in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PPExecuted&lt;/strong&gt; – indicates that progressive profiling information was already gathered from user in current UserJourney. It helps to prevent from asking user for multiple information in single UserJourney execution.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z4cbBM9U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/b2c-progressive-profiling-user-journey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z4cbBM9U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://melmanm.github.io/assets/img/article1/b2c-progressive-profiling-user-journey.png" alt="Azure AD B2C login 2" width="872" height="1332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt; utilizes base self-asserted technical profile which displays sign-in form in the browser. Once user fills email and password &lt;em&gt;SelfAsserted-LocalAccountSignin-Email&lt;/em&gt; credentials are validated by executing ROPC (Resource Owner Password Credentials) flow. ROPC flow gets user access token form &lt;a href="https://login.microsoftonline.com/%7Btenant%7D/oauth2/token"&gt;https://login.microsoftonline.com/{tenant}/oauth2/token&lt;/a&gt; endpoint. Received token indludes &lt;em&gt;objectId&lt;/em&gt; attribute, which identifies user in AD tenant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt; is not directly related with progressive profiling and it is excluded from diagram. Step 2 guides user through sign-up action in case user account does not exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt; reads user related data from AD tenant. It uses extended base &lt;em&gt;AAD-UserReadUsingObjectId&lt;/em&gt; TechnicalProfile to gather user data based on &lt;em&gt;objectId&lt;/em&gt; from Step1.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;B2C_1A_ ProgressiveProfile_TrustFrameworkBase&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;TechnicalProfile&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"AAD-UserReadUsingObjectId"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputClaims&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OutputClaim&lt;/span&gt; &lt;span class="na"&gt;ClaimTypeReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"extension_PPCounter"&lt;/span&gt; &lt;span class="na"&gt;DefaultValue=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/OutputClaims&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputClaimsTransformations&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OutputClaimsTransformation&lt;/span&gt; &lt;span class="na"&gt;ReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"IncrementProgressiveProfileCounter"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OutputClaimsTransformation&lt;/span&gt; &lt;span class="na"&gt;ReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"SetProgressiveProfilingShouldExecute"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/OutputClaimsTransformations&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/TechnicalProfile&amp;gt;&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;em&gt;PPCounter&lt;/em&gt; claim is read from AD. If it does not exist its value is set to 0. Additionally, two output claims transformations are executed:&lt;br&gt;&lt;br&gt;
&lt;em&gt;PPCounter&lt;/em&gt; claim is incremented&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ClaimsTransformation&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"IncrementProgressiveProfileCounter"&lt;/span&gt; &lt;span class="na"&gt;TransformationMethod=&lt;/span&gt;&lt;span class="s"&gt;"AdjustNumber"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;InputClaims&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;InputClaim&lt;/span&gt; &lt;span class="na"&gt;ClaimTypeReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"extension_PPCounter"&lt;/span&gt; &lt;span class="na"&gt;TransformationClaimType=&lt;/span&gt;&lt;span class="s"&gt;"inputClaim"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/InputClaims&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;InputParameters&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;InputParameter&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Operator"&lt;/span&gt; &lt;span class="na"&gt;DataType=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"INCREMENT"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/InputParameters&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputClaims&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OutputClaim&lt;/span&gt; &lt;span class="na"&gt;ClaimTypeReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"extension_PPCounter"&lt;/span&gt; &lt;span class="na"&gt;TransformationClaimType=&lt;/span&gt;&lt;span class="s"&gt;"outputClaim"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/OutputClaims&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ClaimsTransformation&amp;gt;&lt;/span&gt; 

&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;em&gt;PPShouldExecute&lt;/em&gt; claim is added and set to &lt;em&gt;true&lt;/em&gt; when &lt;em&gt;PPCounter&lt;/em&gt; equals 3. &lt;strong&gt;It enables progressive profiling prompt on every third log-in&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ClaimsTransformation&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"SetProgressiveProfilingShouldExecute"&lt;/span&gt; &lt;span class="na"&gt;TransformationMethod=&lt;/span&gt;&lt;span class="s"&gt;"AssertNumber"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;InputClaims&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;InputClaim&lt;/span&gt; &lt;span class="na"&gt;ClaimTypeReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"extension_PPCounter"&lt;/span&gt; &lt;span class="na"&gt;TransformationClaimType=&lt;/span&gt;&lt;span class="s"&gt;"inputClaim"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/InputClaims&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;InputParameters&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;InputParameter&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Operator"&lt;/span&gt; &lt;span class="na"&gt;DataType=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"GreaterThanOrEqual"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;InputParameter&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"CompareToValue"&lt;/span&gt; &lt;span class="na"&gt;DataType=&lt;/span&gt;&lt;span class="s"&gt;"int"&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;InputParameter&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"throwError"&lt;/span&gt; &lt;span class="na"&gt;DataType=&lt;/span&gt;&lt;span class="s"&gt;"boolean"&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/InputParameters&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputClaims&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OutputClaim&lt;/span&gt; &lt;span class="na"&gt;ClaimTypeReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"PPShouldExecute"&lt;/span&gt; &lt;span class="na"&gt;TransformationClaimType=&lt;/span&gt;&lt;span class="s"&gt;"outputClaim"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/OutputClaims&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ClaimsTransformation&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ProgressiveProfile_TrustFrameworkExtensions&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;TechnicalProfile&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"AAD-UserReadUsingObjectId"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputClaims&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OutputClaim&lt;/span&gt; &lt;span class="na"&gt;ClaimTypeReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"extension_PreferedTraining"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;OutputClaim&lt;/span&gt; &lt;span class="na"&gt;ClaimTypeReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"extension_PreferedExcercises"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/OutputClaims&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/TechnicalProfile&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Progressive profiling, implementation specific, claims are read from AD as well. &lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt; executes SubJourney which is designed to gather and store user preferences. Each Orchestration Step correspond with single progressive profiling claim. Its structure can be easily extended when new application specific claims are added. SubJourney is executed when &lt;em&gt;PPShouldExecute&lt;/em&gt; equals true.&lt;/p&gt;

&lt;p&gt;SubJourney Steps are skipped if corresponding claim is already set or different claim was set in current UserJourney.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;OrchestrationStep&lt;/span&gt; &lt;span class="na"&gt;Order=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;Type=&lt;/span&gt;&lt;span class="s"&gt;"ClaimsExchange"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Preconditions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Precondition&lt;/span&gt; &lt;span class="na"&gt;Type=&lt;/span&gt;&lt;span class="s"&gt;"ClaimsExist"&lt;/span&gt; &lt;span class="na"&gt;ExecuteActionsIf=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Value&amp;gt;&lt;/span&gt;extension_PreferedTraining&lt;span class="nt"&gt;&amp;lt;/Value&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Action&amp;gt;&lt;/span&gt;SkipThisOrchestrationStep&lt;span class="nt"&gt;&amp;lt;/Action&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/Precondition&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Precondition&lt;/span&gt; &lt;span class="na"&gt;Type=&lt;/span&gt;&lt;span class="s"&gt;"ClaimsExist"&lt;/span&gt; &lt;span class="na"&gt;ExecuteActionsIf=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Value&amp;gt;&lt;/span&gt;PPExecuted&lt;span class="nt"&gt;&amp;lt;/Value&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Action&amp;gt;&lt;/span&gt;SkipThisOrchestrationStep&lt;span class="nt"&gt;&amp;lt;/Action&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/Precondition&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Preconditions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ClaimsExchanges&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ClaimsExchange&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"ProfileUpdateExchangePreferedTraining"&lt;/span&gt; &lt;span class="na"&gt;TechnicalProfileReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"SelfAsserted-ProgressiveProfiling-PreferedTraining"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ClaimsExchanges&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/OrchestrationStep&amp;gt;&lt;/span&gt; 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It calls &lt;em&gt;SelfAsserted&lt;/em&gt; technical profile which displays progressive profiling form and saves selected value in output claim. Additionaly, technical profile sets &lt;em&gt;PPExecuted&lt;/em&gt; output claim to true. It prevents from showing next progressive profiling forms during the same sign-in flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5&lt;/strong&gt; writes output claims associated with user to Azure AD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6&lt;/strong&gt; resets (deletes) &lt;em&gt;PPCounter&lt;/em&gt; claim from AD if progressive profiling SubJourney were executed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;OrchestrationStep&lt;/span&gt; &lt;span class="na"&gt;Order=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;Type=&lt;/span&gt;&lt;span class="s"&gt;"ClaimsExchange"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Preconditions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Precondition&lt;/span&gt; &lt;span class="na"&gt;Type=&lt;/span&gt;&lt;span class="s"&gt;"ClaimEquals"&lt;/span&gt; &lt;span class="na"&gt;ExecuteActionsIf=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Value&amp;gt;&lt;/span&gt;PPExecuted&lt;span class="nt"&gt;&amp;lt;/Value&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Value&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/Value&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Action&amp;gt;&lt;/span&gt;SkipThisOrchestrationStep&lt;span class="nt"&gt;&amp;lt;/Action&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/Precondition&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Preconditions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ClaimsExchanges&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ClaimsExchange&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"DeleteClaimUpdateExchange"&lt;/span&gt; &lt;span class="na"&gt;TechnicalProfileReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"AAD-DeleteProgressiveProfilingCounter"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ClaimsExchanges&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/OrchestrationStep&amp;gt;&lt;/span&gt; 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 7&lt;/strong&gt; creates JWT token based on current output claims.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All policy configuration files are available on my &lt;a href="https://github.com/melmanm/azure-b2c-custom-policy-progressive-profiling"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>azureadb2c</category>
      <category>azure</category>
      <category>identity</category>
      <category>oauth</category>
    </item>
  </channel>
</rss>
