OWIN openid connect external login doesn't execute specified callback url

All we need is an easy explanation of the problem, so here it is.

I am using owin openid connect authentication where the authentication provider is hosted on a separate domain. The authentication process works nicely. I am able to view restricted pages upon successful login at the identity server.

But I want the external identity server to return back to “account/SignInCallback” controller action so that I can execute a few lines of code relevant for the member’s account. In the browser’s network activity it shows me “302 Found” for the “account/SignInCallback” but it doesn’t hit the breakpoints attached to it. It directly goes to the request initiating url e.g. “account/Dashboard”.

Is there an way I can force the system to return back to the specific url after login, even though requesting url was different?

public class AccountController : BaseController
{
    public AccountController() : base()
    {
    }

    [Authorize]
    public ActionResult Dashboard()
    {
        return View();
    }

    [HttpPost]
    [AllowAnonymous]
    public ActionResult SignInCallback()
    {
        if (User.Identity.IsAuthenticated)
        {
            // Read claims and execute member specific codes
        }
        return View();
    }

    [AllowAnonymous]
    public ActionResult Unauthorized()
    {
        return View();
    }
}

The startup class is below:

public sealed class Startup
{   
    public void Configuration(IAppBuilder app)
    {
        string ClientCallbackUri = @"https://client.local/account/SignInCallback";
        string IdServBaseUri = @"https://idm.website.com/core";
        string TokenEndpoint = @"https://idm.website.com/core/connect/token";
        string UserInfoEndpoint = @"https://idm.website.com/core/connect/userinfo";
        string ClientId = @"WebPortalDemo";
        string ClientSecret = @"aG90apW2+DbX1wVnwwLD+eu17g3vPRIg7p1OnzT14TE=";

        JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "Cookies"
        });

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            ClientId = ClientId,
            Authority = IdServBaseUri,
            RedirectUri = ClientCallbackUri,
            PostLogoutRedirectUri = ClientUri,
            ResponseType = "code id_token token",
            Scope = "openid profile roles",
            TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = "name",
                RoleClaimType = "role"
            },
            SignInAsAuthenticationType = "Cookies",

            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthorizationCodeReceived = async n =>
                {
                    // use the code to get the access and refresh token
                    var tokenClient = new TokenClient(
                        TokenEndpoint,
                        ClientId,
                        ClientSecret);

                    var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);

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

                    // use the access token to retrieve claims from userinfo
                    var userInfoClient = new UserInfoClient(UserInfoEndpoint);

                    var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);

                    // create new identity
                    var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
                    //id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
                    id.AddClaims(userInfoResponse.Claims);

                    id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
                    id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
                    id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                    id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
                    id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));

                    n.AuthenticationTicket = new AuthenticationTicket(
                        new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
                        n.AuthenticationTicket.Properties);
                }
            }
        });
    }
}

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

It looks all you need is to set

n.AuthenticationTicket.Properties.RedirectUri = n.RedirectUri;

in your AuthorizationCodeReceived delegate

Method 2

The individual auth templates do this by enabling AutomaticChallenge on the cookie middleware rather than the other auth middleware (OIDC in this case). Cookie redirects them to an AccountController login page, then they select the auth method, do the auth redirects, return to the account controller for the additional steps you want to add, and then they finish by redirecting back to the original page.

Here’s a later version of that template for ASP.NET Core:
https://github.com/aspnet/Templates/blob/rel/1.0.5/src/Rules/StarterWeb/IndividualAuth/Controllers/AccountController.cs
https://github.com/aspnet/Templates/blob/rel/1.0.5/src/Rules/StarterWeb/IndividualAuth/Startup.cs

Note much of this is managed by the Identity framework, but it’s not required.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply