I have setup 3 projects in visual studio as follows:
WebServer
port 5002 which generates the web client pagesAPIServer
port 7288 where the web server queries the API endpoints and the database to get data into the viewsAuthorisationServer
port 5001 where it is used to log the user into the application and authenticate/authoriseWebServer
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
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