1

I have built a .NET Core 6 app, with an external identity provider. I've used IdentityServer4 as the identity provider. So far what I built almost works. If you go to the login-page, choose the external login, you are redirected to my identity server. Here you login, and you're redirected back to my app. Here ASP.NET Core Identity uses the token to log you into the app, this works.

However if you (in unauthenticated state) try to access a view with the Authorize-attribute on it, you're redirected to the identity server, here you log in, redirects you back to the app, but then does not log you into the app. It sends you back to the identity server and then the endless loop begins.

I'm using the default ASP.NET Core Identity Razor pages - I've tried scaffolding them to debug what happens. And as far as I can see, when I manually log in. All the necessary steps happen. The redirect back to my app, using the identity token to log in. None of this happens, when I hit a page with the Authorize attribute.

Also when I hit the page with the Authorize attribute, I'm sent directly to my identity server login page, not the login page in my app.

Any ideas?

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("MvcClientContextConnection") ?? throw new InvalidOperationException("Connection string 'MvcClientContextConnection' not found.");

builder.Services.AddDbContext<MvcClientContext>(options =>
    options.UseSqlServer(connectionString));

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = "Cookies";
    options.DefaultChallengeScheme = "oidc";
})
    .AddCookie("Cookies")
    .AddOpenIdConnect("oidc", options =>
    {
        options.SaveTokens = true;

        options.Authority = "https://localhost:5001";
        options.ClientId = "mvc";
        options.ClientSecret = "secret";
        options.ResponseType = OpenIdConnectResponseType.Code;

        options.Scope.Add("profile");
        options.Scope.Add("email");
        options.GetClaimsFromUserInfoEndpoint = true;

        options.RequireHttpsMetadata = false;
    });

    builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = false)
        .AddEntityFrameworkStores<MvcClientContext>()
        .AddDefaultTokenProviders();

I've checked that cookies are set - and I have the following cookies. I'm not sure, what to expect.

enter image description here

Also I've tried debugging the OnTokenReponseReceived event - to see if I receive a token. And I do. Both an Access token and an Id token. I've tried inspecting them. And as far as I can tell there is no skewing of time. And the values I would expect are there.

options.Events.OnTokenResponseReceived = n =>
        {
            return Task.FromResult(0);
        };

The Id-token:

{
  "nbf": 1655641328,
  "exp": 1655641628,
  "iss": "https://localhost:5001",
  "aud": "mvc",
  "nonce": "637912381209176720.ZDIwZWJhZjUtYWJkNi00OWY2LTliZDMtYjdhNjI3NTA3YjFkNjdlOGIxMDQtNmUwYy00ZmUxLTk4MzUtNDI5YjEwODZkOGRh",
  "iat": 1655641328,
  "at_hash": "UJwjVbUn4onLus-a6Wo8qA",
  "s_hash": "UAsrV-r-CNCIK73V1KT0iw",
  "sid": "6E7E25E03BFD3D1BEDB2FE6980EC9287",
  "sub": "3d8f3e39-d9c4-4e75-88f4-07d359e21052",
  "auth_time": 1655640810,
  "idp": "local",
  "name": "myuser",
  "email": "myuser@mysite.dk",
  "preferred_username": "myuser@mysite.dk",
  "email_verified": true,
  "amr": [
    "pwd"
  ]
}
Bildsoe
  • 1,310
  • 6
  • 31
  • 44
  • Did you add the following middlewares before mapping your controller routes? app.UseAuthentication(); app.UseAuthorization(); – madmax Jun 21 '22 at 06:26
  • Yes, I did. But earlier I suspected it might have to do with the middleware, as I want to support both local login and external. The strange thing is that if I use the "OpenId Connect"-button on the local login page it works. However if the Authorize-handler detects that I'm not logged in, it redirects me directly to the external login provider. I was expecting it to redirect me to the local page, where I could select either local og external login. – Bildsoe Jun 22 '22 at 06:58
  • Read the accepted answer from https://stackoverflow.com/questions/52492666/what-is-the-point-of-configuring-defaultscheme-and-defaultchallengescheme-on-asp –  Jun 24 '22 at 05:29

2 Answers2

1

If there's anything I've learned from the past 2 weeks of struggling with IdentityServer4 is do not go against the grain!

I would make some modifications to your code to remove some, possibly unintentional, probably unnecessary customizations.

  1. First of all, remove the call to AddCookie("Cookies")
  2. Remove customization of AddAuthentication, simply call AddAuthentication() without any customization.
  3. In your /Account/Login action, return a challenge to the oidc scheme.

Hope this helps.

Aviad P.
  • 32,036
  • 14
  • 103
  • 124
0

Please try these:

A challenge is issued and since the cookie authentication middleware is configured with AutomaticChallenge = true it will handle it. The cookie middleware responds to a challenge by redirecting the user to the LoginPath (by creating a response with status code 302 and a Location header for /account/login).

Alternatively, if your authentication middleware is not set with AutomaticChallenge = true and you want to “challenge” it you can specify the AuthenticationScheme:

[Authorize(ActiveAuthenticationSchemes="MyCookie")]
public IActionResult ActionThatRequiresAnAuthenticatedUser()
{
    //...
}

An external login provider “remembers” that you’ve already signed in. That’s why if you already signed in to, for example Facebook, and you use a web app that allows you to sign in with Facebook, you’ll be redirected to Facebook and then immediately back to the web app (assuming you’ve already authorized the web app in Facebook). If you don’t have enough permissions this can cause a redirect loop.

External Login Providers in ASP.NET Core

The redirect loop problem happens when you have an authenticated user without the required privileges. For example, if you are using roles and you annotate a controller action with the authorize attribute and specify the role “Admin”.

[Authorize(Roles="Admin")]
public ActionResult Users() 
{
    ...
}

Authorization redirect loops in ASP.NET MVC

Infinite login redirect loop with Google and ASP.NET Core Identity

New Asp.Net MVC5 project produces an infinite loop to login page provided different methods to solve issue.

Eskandar Abedini
  • 2,090
  • 2
  • 13
  • I'm going to test this today (and read the BlinkingCaret blogpost). I'm not using roles - what would cause, the "If you don’t have enough permissions..." to occur. As I understand it, the Authorize-attribute with no roles set, will just look for an authenticated user? – Bildsoe Jun 27 '22 at 09:39
  • First read the documents then try with `[AllowAnonymous]` - [How to allow an anonymous user access to some given page in MVC?](https://stackoverflow.com/questions/9727509/how-to-allow-an-anonymous-user-access-to-some-given-page-in-mvc) – Eskandar Abedini Jun 27 '22 at 09:45
  • I don't want to give an anonymous user access to the page. I have a custom permissions system I want to use, but this is not in the code at the moment. At this point I'm only trying to verify that a user is logged in. – Bildsoe Jun 27 '22 at 10:02
  • Try it temporarily – Eskandar Abedini Jun 27 '22 at 10:15