Both OIDC and ADFS in same application (OWIN)

Viewed 110

I am working on an ASP.NET MVC 5 website using EPiServer CMS.

The requirement is that we have admin (back-end users) log in using ADFS (working) and OIDC for front-end.

Both are set to passive mode and are called through action methods in a controller.

The issue I am facing is that front-end users OIDC is set to (Authentication type ="Federation") when it returns from external call instead of external cookie.

public void Configuration(IAppBuilder app)
{
    //IdentityModelEventSource.ShowPII = true;
    ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11;

    ConfigureMemberAuth(app);

    ConfigureAdminAuth(app);
}

private void ConfigureMemberAuth(IAppBuilder app)
{
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/logmein/"),
        LogoutPath = new PathString("/"),
        AuthenticationMode = AuthenticationMode.Active

        });

    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

    _ = app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
    {
        ClientId = ApplicationSettings.OIDCClientId,
        ClientSecret = ApplicationSettings.OIDCClientSecret,
        Authority = ApplicationSettings.OIDCAuthority,
        RedirectUri = $"{ApplicationSettings.Domain}{_oidcRedirectMethod}",
        PostLogoutRedirectUri = ApplicationSettings.Domain,
        ResponseType = OpenIdConnectResponseType.CodeIdToken,
        Scope = "xxx",
        SaveTokens = true,
        AuthenticationMode = AuthenticationMode.Passive,
        AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,
        
        Notifications = new OpenIdConnectAuthenticationNotifications
        {
            AuthenticationFailed = notification =>
            {
                if (string.Equals(notification.ProtocolMessage.Error, "xxx", StringComparison.Ordinal))
                {
                    notification.HandleResponse();

                    if (string.Equals(notification.ProtocolMessage.ErrorDescription, "xxx", StringComparison.Ordinal))
                    notification.Response.Redirect(ApplicationSettings.Domain);
                    else
                    {
                        var errorPage = UrlResolver.GetUrl(PageHelper.GetAdminPage().LoginBox.NemIDErrorPage.GetFriendlyUrl());
                        notification.Response.Redirect(errorPage);
                    }
                }

                return Task.CompletedTask;
            },

                // Retrieve an access token from the remote token endpoint
                // using the authorization code received during the current request.
            AuthorizationCodeReceived = async notification =>
            {
                using (var client = new HttpClient())
                {
                    var configuration = await notification.Options.ConfigurationManager.GetConfigurationAsync(notification.Request.CallCancelled);
                    var tokenEndpointResult = await ExchangeCodeForTokens(notification, client, configuration);

                        // Add the identity token to the returned ClaimsIdentity to make it easier to retrieve.
                    notification.AuthenticationTicket.Identity.AddClaim(new Claim(
                        type: OpenIdConnectParameterNames.IdToken,
                        value: tokenEndpointResult.Value<string>(OpenIdConnectParameterNames.IdToken)));

                        // Retrieve the claims from UserInfo endpoint using the access token as bearer token.
                    var accesstoken = tokenEndpointResult.Value<string>(OpenIdConnectParameterNames.AccessToken);
                    var userInfoEndpointResult = await UserInfoEndpointClaims(notification, client, configuration, accesstoken);

                        //Security note: It is important to verify that the sub claim from ID token matches the sub claim in the UserInfo response
                    var userinfoSub = userInfoEndpointResult["xx"].Value<string>();
                    var idtokenSub = notification.AuthenticationTicket.Identity.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value;
                    if (userinfoSub == idtokenSub)
                    {
                            //add claims from UserInfo endpoint to identity
                        foreach (var entry in userInfoEndpointResult)
                        {
                            if (!notification.AuthenticationTicket.Identity.HasClaim(c => c.Type == entry.Key))
                            {
                                notification.AuthenticationTicket.Identity.AddClaim(new Claim(
                                    type: entry.Key,
                                    value: entry.Value.ToString()));
                            }
                        }
                            // Add access token to claims.
                        notification.AuthenticationTicket.Identity.AddClaim(new Claim(
                            OpenIdConnectParameterNames.AccessToken,
                            accesstoken));
                    }
                }
            },

                // Attach the id_token stored in the authentication cookie to the logout request.
            RedirectToIdentityProvider = notification =>
            {
                if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                {
                    var token = notification.OwinContext.Authentication.User?.FindFirst(OpenIdConnectParameterNames.IdToken);
                    if (token != null)
                    {
                        notification.ProtocolMessage.IdTokenHint = token.Value;
                    }

                    notification.Response.Redirect(ApplicationSettings.Domain);
                }

                return Task.CompletedTask;
            },

        }
        });

}

private void ConfigureAdminAuth(IAppBuilder app)
{
    //Enable cookie authentication, used to store the claims between requests

    app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
        CookieName = WsFederationAuthenticationDefaults.CookieName,
        AuthenticationMode = AuthenticationMode.Active,
        CookieHttpOnly = true,
        });

        //Enable federated authentication
    _ = app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions()
    {
            //Trusted URL to federation server meta data
        MetadataAddress = ApplicationSettings.MetaDataAddress,
            //Value of Wtreal must *exactly* match what is configured in the federation server
        Wtrealm = ApplicationSettings.RelyPartyUri,
        AuthenticationMode = AuthenticationMode.Passive,

        Notifications = new WsFederationAuthenticationNotifications()
        {
            RedirectToIdentityProvider = (ctx) =>
            {
                    //  To avoid a redirect loop to the federation server send 403 when user is authenticated but does not have access
                if (ctx.OwinContext.Response.StatusCode == 401 && (ctx.OwinContext.Authentication.User.Identity.AuthenticationType == WsFederationAuthenticationDefaults.AuthenticationType && ctx.OwinContext.Authentication.User.Identity.IsAuthenticated))
                {
                    ctx.OwinContext.Response.StatusCode = 403;
                    ctx.HandleResponse();
                }
                    //XHR requests cannot handle redirects to a login screen, return 401
                if (ctx.OwinContext.Response.StatusCode == 401 && IsXhrRequest(ctx.OwinContext.Request))
                {
                    ctx.HandleResponse();
                }
                return Task.FromResult(0);
            },
            SecurityTokenValidated = (ctx) =>
            {

                    //Ignore scheme/host name in redirect Uri to make sure a redirect to HTTPS does not redirect back to HTTP
                var redirectUri = new Uri(ctx.AuthenticationTicket.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
                if (redirectUri.IsAbsoluteUri)
                {
                    ctx.AuthenticationTicket.Properties.RedirectUri = redirectUri.PathAndQuery;
                }
                    //Sync user and the roles to EPiServer in the background
                ServiceLocator.Current.GetInstance<ISynchronizingUserService>().SynchronizeAsync(ctx.AuthenticationTicket.Identity);
                return Task.FromResult(0);
            },

        }
        });
        //Add stage marker to make sure WsFederation runs on Authenticate (before URL Authorization and virtual roles)
    app.UseStageMarker(PipelineStage.Authenticate);

        // Remap logout to a federated logout
    app.Map(LogoutUrl, map =>
    {
        map.Run(ctx =>
        {
            ctx.Authentication.SignOut();
            ctx.Response.Redirect(ApplicationSettings.Domain);
            return Task.FromResult(0);
            });
        });
}

When I out comment this line: app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);

in admin auth then client-login works as intended and authentication type is set to "ExternalCookie" when call return but then admin login stops working ?

Any help would be appreciated

0 Answers
Related