'Allow the user to try again to connect after authentication failed access_denied

I have a web application that uses OpenIdConnectAuthenticationOptions to connect through Okta. Everything works as expected. When a user is connecting through Okta, he is correctly authenticated. And, in another scenario, when the user is not authorized for this specific application (on the Okta side, the user should be assigned on the app) the authentication failed (error: access_denied) and he is redirected to a custom error page. Look at the below code in the 'AuthenticationFailed' part.

Now I would like to create a link on this custom error page to allow the user to try again to connect. This link (try again link in the code below) points to the root of the web application. Unfortunately it doesn't work. I mean, all the time, we fallback on the AuthenticationFailed notification. It seems the connection information are still in the browser cache.

How to proceed (after AuthenticationFailed access_denied) to clear all the information and allow the user to try again from the start by clicking the link pointing to the root of the web app ?

        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            ClientId = clientId,
            ClientSecret = clientSecret,
            Authority = authority,
            RedirectUri = redirectUri,
            ResponseType = OpenIdConnectResponseType.CodeIdToken,
            Scope = OpenIdConnectScope.OpenIdProfile,
            PostLogoutRedirectUri = postLogoutRedirectUri,
            TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = "preferred_username"
            },

            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthorizationCodeReceived = async n =>
                {
                    // Exchange code for access and ID tokens
                    var tokenClient = new TokenClient(authority + "/v1/token", clientId, clientSecret);
                    var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, redirectUri);

                    if (tokenResponse.IsError)
                    {
                        throw new Exception(tokenResponse.Error);
                    }

                    var userInfoClient = new UserInfoClient(authority + "/v1/userinfo");
                    var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
                    var claims = new List<Claim>();
                    claims.AddRange(userInfoResponse.Claims);
                    claims.Add(new Claim("id_token", tokenResponse.IdentityToken));
                    claims.Add(new Claim("access_token", tokenResponse.AccessToken));

                    if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
                    {
                        claims.Add(new Claim("refresh_token", tokenResponse.RefreshToken));
                    }
                    
                    n.AuthenticationTicket.Identity.AddClaims(claims);
                    return;
                },

                RedirectToIdentityProvider = n =>
                {
                    // If signing out, add the id_token_hint
                    if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                    {
                        var idTokenClaim = n.OwinContext.Authentication.User.FindFirst("id_token");

                        if (idTokenClaim != null)
                        {
                            n.ProtocolMessage.IdTokenHint = idTokenClaim.Value;
                        }
                    }
                    return Task.CompletedTask;
                },

                AuthenticationFailed = (context) => 
                {
                    string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
                    string error_type = context.ProtocolMessage.Error;
                    string error_description = context.ProtocolMessage.ErrorDescription;

                    context.ProtocolMessage.RedirectUri = appBaseUrl + "/AuthenticationFailed.aspx?error_type=" + error_type + "&error_description=" + error_description;
                    context.HandleResponse();
                    context.Response.Redirect(context.ProtocolMessage.RedirectUri);

                    return Task.FromResult(0);
                }
            }
        });

Code of the AuthenticationFailed.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AuthenticationFailed.aspx.cs" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>App</title>
</head>
<body>
    <div class="main">
        <h2>My App</h2>
        <h3>Authentication failed</h3>
        <h4>Error Type: <% Response.Write(Request.QueryString["error_type"]); %> </h4>
        <h4>Error description: <% Response.Write(Request.QueryString["error_description"]); %></h4>
    </div>
    <a href="https://localhost/myWebApp/">Try again</a>
</body>
</html>


Solution 1:[1]

In your Startup.cs, separate the failed status into another method like this:

        public void Configuration(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
            app.UseCookieAuthentication(new CookieAuthenticationOptions());
            app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                // Sets the ClientId, authority, RedirectUri as obtained from web.config
                ClientId = clientId,
                Authority = authority,
                RedirectUri = redirectUri,
                // PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
                PostLogoutRedirectUri = redirectUri,
                Scope = OpenIdConnectScope.OpenIdProfile,
                // ResponseType is set to request the code id_token - which contains basic information about the signed-in user
                ResponseType = OpenIdConnectResponseType.CodeIdToken,
                // OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthenticationFailed = OnAuthenticationFailed
                }
            }
          );
        }

        /// <summary>
        /// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
        {
            context.HandleResponse();
            context.Response.Redirect("/?errormessage=" + context.Exception.Message);
            return Task.FromResult(0);
        }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 smoore4