ASP.NET Core 8 and Duende Identity Server Authentication Schemes (OpenIdConnect, Cookies, JWT)

921 views Asked by At

I have setup 3 projects in visual studio as follows:

  • WebServer port 5002 which generates the web client pages
  • APIServer port 7288 where the web server queries the API endpoints and the database to get data into the views
  • AuthorisationServer port 5001 where it is used to log the user into the application and authenticate/authorise

WebServer uses the cookies and OpenIdConnect authentication schemes. The configuration of the authentication schemes is done in Program.cs:

builder.Services.AddHttpClient("APIClient", client =>
{
    client.BaseAddress = new Uri(builder.Configuration["WarehouseWebAPIRoot"]);
    client.DefaultRequestHeaders.Clear();
    client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
}).AddUserAccessTokenHandler();

builder.Services.AddHttpClient("IDPClient", client =>
  {
    client.BaseAddress = new Uri("https://localhost:5001/");
  });

builder.Services.AddAccessTokenManagement(options =>
  {
    options.Client.Clients.Add("identityserver", new 
  IdentityModel.Client.ClientCredentialsTokenRequest
    {
        Address = "https://localhost:5001/connect/token",
        ClientId = "web",
                    ClientSecret = "secret",
        Scope = "WebAppApi:fullaccess"
    });
   });   

builder.Services.AddAuthentication(options =>
 {
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
   })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { 
        options.AccessDeniedPath = "/Authentication/AccessDenied";
        options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
        options.SlidingExpiration = true;
        options.Cookie.MaxAge = options.ExpireTimeSpan;
        options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
    })
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
    {   options.AccessDeniedPath= "/Authentication/AccessDenied";
        options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.Authority = "https://localhost:5001";
      
    options.ClientId = builder.Configuration["Authentication:OpenId:ClientId"];
        options.ClientSecret = builder.Configuration["Authentication:OpenId:ClientSecret"];
        options.ResponseType = "code";
        options.GetClaimsFromUserInfoEndpoint = true;
        options.SaveTokens = true;
        options.Scope.Add("roles");
          options.Scope.Add("profile");
        options.Scope.Add("WebAppApi:fullaccess");
        
        options.ClaimActions.Remove("aud");
        options.ClaimActions.DeleteClaim("sid");
        options.ClaimActions.DeleteClaim("idp");
        
        options.ClaimActions.MapJsonKey("role", "role");
        options.TokenValidationParameters = new()
        {
           
            NameClaimType = JwtClaimTypes.Name,
            RoleClaimType = JwtClaimTypes.Role,
           
        };
    });

builder.Services.AddAuthorization(options =>
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("role", "Admin");
        
    })
);

APIServer uses the JWTBearer authentication scheme:

builder.Services.AddAuthentication(o =>
{
    o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
    {
        options.SaveToken = true;
        options.Authority = builder.Configuration["Authentication:OpenId:Authority"];
        options.Audience = builder.Configuration["Authentication:OpenId:Audience"];
        options.Configuration?.ClaimsSupported.Add("role");
        options.TokenValidationParameters = new()
        {

            ValidateAudience = false,
            NameClaimType = JwtClaimTypes.Name,
            RoleClaimType = JwtClaimTypes.Role,
            //ValidTypes = new[] { "at+jwt" }
        };
    });

builder.Services.AddAuthorization(options =>
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("role", "Admin");
    })
);

AuthorisationServer uses the cookies and OpenIdConnect authentication schemes. Configuration is done in Program.cs and Config.cs. I can share these if more details are needed.

The user is not authenticated and the logs from the Duende Identity Server are as follows:

[23:54:57 Debug] Duende.IdentityServer.Endpoints.AuthorizeEndpoint ValidatedAuthorizeRequest {"ClientId": "web", "ClientName": null, "RedirectUri": "https://localhost:5002/signin-oidc", "AllowedRedirectUris": ["https://localhost:5002/signin-oidc"], "SubjectId": "anonymous", "ResponseType": "code", "ResponseMode": "form_post", "GrantType": "authorization_code", "RequestedScopes": "openid profile roles WebAppApi:fullaccess", "State": "CfDJ8Pu7Fp24kUpGjM8vsRLlDAbPNrdC_l3Qky59ofw-LCER_PUh03HZdP-Nii7JanPnvqKm-MD7_gdrPqWdZpV6NgIUe66T6MZvJZ3XkisfAaCBsYMKDJG5kji8e1hs-I1xlYy1Oy-_9hxN_X4hlrA9rol24B-GnFKFTMXJBSzSiQuUjr5-kt0f6RjFmUBJ1z48i2R2WqNsvE6SKKn9CoAlNCjrsUa-GKVlR6Yi1t0TxL3HqY4M7IKCVvj4fPOgxqNFpa_tbqEUc6mcaXNsDzIY1hvqHkdhtgzZPY53B57ETAdBXrGQG7uhZJWqIlXJEcxy0_0hvMh0Jgle0MoYSLrEdGEFbu11Q0-FxjUVDq6xYkjhQIKyBHbo-VwYiaBa8aL1hA", "UiLocales": null, "Nonce": "638364596969981993.NTJjNzQzYzMtNDM2Ny00Nzc2LWI0ZDYtZWQ2M2I4MTg0YTgwZWEwODIzMjctMTQ2ZC00MTdlLThkOTMtZWM5MGM5MjUyNDg0", "AuthenticationContextReferenceClasses": null, "DisplayMode": null, "PromptMode": "", "MaxAge": null, "LoginHint": null, "SessionId": "", "Raw": {"client_id": "web", "redirect_uri": "https://localhost:5002/signin-oidc", "response_type": "code", "scope": "openid profile roles WebAppApi:fullaccess", "code_challenge": "5m7NDLdV1So0go6tHUS7pI6EFnlxp5Py71xdXkRmObk", "code_challenge_method": "S256", "response_mode": "form_post", "nonce": "638364596969981993.NTJjNzQzYzMtNDM2Ny00Nzc2LWI0ZDYtZWQ2M2I4MTg0YTgwZWEwODIzMjctMTQ2ZC00MTdlLThkOTMtZWM5MGM5MjUyNDg0", "state": "CfDJ8Pu7Fp24kUpGjM8vsRLlDAbPNrdC_l3Qky59ofw-LCER_PUh03HZdP-Nii7JanPnvqKm-MD7_gdrPqWdZpV6NgIUe66T6MZvJZ3XkisfAaCBsYMKDJG5kji8e1hs-I1xlYy1Oy-_9hxN_X4hlrA9rol24B-GnFKFTMXJBSzSiQuUjr5-kt0f6RjFmUBJ1z48i2R2WqNsvE6SKKn9CoAlNCjrsUa-GKVlR6Yi1t0TxL3HqY4M7IKCVvj4fPOgxqNFpa_tbqEUc6mcaXNsDzIY1hvqHkdhtgzZPY53B57ETAdBXrGQG7uhZJWqIlXJEcxy0_0hvMh0Jgle0MoYSLrEdGEFbu11Q0-FxjUVDq6xYkjhQIKyBHbo-VwYiaBa8aL1hA", "x-client-SKU": "ID_NET8_0", "x-client-ver": "7.0.3.0"}, "$type": "AuthorizeRequestValidationLog"} 23:54:57 Information] Duende.IdentityServer.ResponseHandling.AuthorizeInteractionResponseGenerator Showing login: User is not authenticated

[23:54:57 Information] Serilog.AspNetCore.RequestLoggingMiddleware HTTP GET /connect/authorize responded 302 in 41.8403 ms

[23:54:57 Information] >Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler Cookies was not authenticated. Failure message: Unprotect ticket failed

[23:54:57 Information] >Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler Cookies was not authenticated. Failure message: Unprotect ticket failed

[23:54:57 Information] >Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler Cookies was not authenticated. Failure message: Unprotect ticket failed

[23:54:57 Information] >Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler Cookies was not authenticated. Failure message: Unprotect ticket failed

[23:54:57 Debug] Duende.IdentityServer.Validation.AuthorizeRequestValidator Start authorize request protocol validation

[23:54:57 Debug] Duende.IdentityServer.Stores.ValidatingClientStore client configuration validation for client web succeeded.

[23:54:57 Debug] Duende.IdentityServer.Validation.AuthorizeRequestValidator Checking for PKCE parameters

[23:54:57 Debug] Duende.IdentityServer.Validation.AuthorizeRequestValidator Calling into custom validator: >Duende.IdentityServer.Validation.DefaultCustomAuthorizeRequestValidator

[23:54:57 Information] Serilog.AspNetCore.RequestLoggingMiddleware HTTP GET /Account/Login responded 200 in 23.9251 ms

Could you please let me know why the user does not get authenticated? I have tried changing the options to the authentication schemes but with no success. I am following the instructions found in the documentation here: https://docs.duendesoftware.com/identityserver/v6/quickstarts/

Could you please let me know what I am missing? Thank you

1

There are 1 answers

4
Tore Nestenius On BEST ANSWER

When you get this error:

>Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler Cookies was not authenticated. Failure message: Unprotect ticket failed

That means that the the instance can't "decrypt" the received cookie. The cookies are protected using the Data Protection API. See my blog post here about how the cookies are protected:

Exploring what is inside the ASP.NET Core cookies

Another cause of trouble can be that the services are using cookies with the same name.

Cookies are shared across all services on the same "domain", meaning that it could be that a service of yours might receive a cookie from another service.

So, ensure that all the cookies in your application is unique per service.

See this answer: Are HTTP cookies port specific?

Third issue is here:

.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { 
    options.AccessDeniedPath = "/Authentication/AccessDenied";
    options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
    options.SlidingExpiration = true;
    options.Cookie.MaxAge = options.ExpireTimeSpan;
    options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
})

The browser might reject the strict cookie because it was "triggered" by another site, you might need to change to samesitemode.lax.

See my blog post here: Debugging cookie problems in ASP.NET Core