API is authorized without the Authorization Headers in Request using Identity Server 4 and .net core Identity - asp.net-core

I am making the API call after the successfully login through Identity server from my vue application (SPA).
Firstly i was adding the Access token in the Header and it was Authorize but i was not getting the claim. Which i have the separate Question on SO, and now i tried by removing the access token from the header during API call the application is still being Authorized.
I don't understand how i should solve the problem.
service.interceptors.request.use(config => {
return authService
.getToken()
.then(tokenResponse => {
app.$Progress.start();
//config.headers.Authorization = `Bearer ${tokenResponse}`; removed Token
return Promise.resolve(config);
})
.catch(error => {
app.prototype.$Progress.fail();
alert("error");
});
});
Oidc Client Manager
export default {
authority: "https://localhost:44305",
client_id: "js",
redirect_uri: `${domain}/authredirect`,
response_type: "id_token token",
scope:"openid profile email api1 role",
post_logout_redirect_uri : `${domain}`,
silent_redirect_uri: `${domain}/silent`,
}
Identity Server Client Configuration
new Client
{
ClientId = "js",
ClientName = "JavaScript Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
AlwaysIncludeUserClaimsInIdToken = true,
RedirectUris = new List<string> {"http://localhost:8080/silent","http://localhost:8080/authredirect"},
PostLogoutRedirectUris = { "http://localhost:8080" },
AllowedCorsOrigins = { "http://localhost:8080" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1",
"role"
}
}
API Configure Services
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore().AddJsonFormatters();
services.AddAuthorization();
services.AddCors(options =>
{
// this defines a CORS policy called "default"
options.AddPolicy("default", policy =>
{
policy.WithOrigins("http://localhost:8080")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<MyContext>(o => o.UseSqlServer(connectionString));
services.AddIdentity<User, IdentityRole<Guid>>().AddEntityFrameworkStores<MyContext>().AddDefaultTokenProviders();
// register the repository
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
services.AddMvcCore().AddJsonFormatters();
}
I have added the Project on Github. Please suggest me something.
Link for Project not available currently, i will add again

I was able to solved the problems on this.
I was missing the DefaultChallengeScheme on my API ConfigureServices
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:44305";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});

Related

ASP.NET Core 3.1: User.Identity.Name is empty in API Controller, but claim name is present

I'm adding Identity Server to my existing project. Basically I have everything in place, but when I do a request to the API, the User.Identity.Name is null. However the User.Identity.Claims contains the name claim:
I'm aware of the way of getting the user name by HttpContext.User.FindFirstValue(ClaimTypes.Name), but it would require a lot of code refactoring, so I'd rather avoid this way.
I configured the ApiResources in Identity server the following way:
public static IEnumerable<ApiResource> ApiResources => new[]
{
new ApiResource
{
Name = "my-api",
DisplayName = "My API",
Description = "My API",
Scopes = new List<string> { "my-api"},
UserClaims = new List<string> {
JwtClaimTypes.Email,
JwtClaimTypes.Name,
JwtClaimTypes.Subject,
JwtClaimTypes.Role,
}
}
};
and the client:
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
// ...
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"name",
"roles",
"my-api",
}
}
};
Authentication setup in API project:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = config["IdentityServer:Domain"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = config["IdentityServer:Domain"],
ValidateAudience = false
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "my-api");
});
});
Please advice what am I doing wrong?
The problem is that Microsoft and OpenID connect have different opinion on what the name of the "name" claim should be. So what you need to do is to tell the system what the name of the name claim is, by:
.AddJwtBearer(opt =>
{
...
opt.TokenValidationParameters.RoleClaimType = "roles";
opt.TokenValidationParameters.NameClaimType = "name";
...
}

IdentityServer4 redirects to Logout page instead of PostLogoutRedirectUri

I created AspNetCore 3.1 Project and added IdentityServer4 for SSO (added 'Microsoft.AspNetCore.ApiAuthorization.IdentityServer' package). Sig-nin works fine, but logout doesn't.
In Startup.cs I have the following configuration for IdentityServer :
public void ConfigureServices(IServiceCollection services)
{
...
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{ ...
var client = new Client
{
ClientName = "ssotestclient",
ClientId = "ssotestclient",
ClientSecrets = { new Secret("somesecret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code.Union(GrantTypes.ResourceOwnerPasswordAndClientCredentials).ToArray(),
RequirePkce = false,
RequireClientSecret = false,
AllowOfflineAccess = false,
AlwaysSendClientClaims = true,
UpdateAccessTokenClaimsOnRefresh = true,
AlwaysIncludeUserClaimsInIdToken = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess
},
RequireConsent = false,
RedirectUris = {"https://mytestsite.local/signin-oidc" },
PostLogoutRedirectUris = {"https://mytestsite.local/signout-callback-oidc"}
}
options.Clients.Add(client);
});
The client Website is an AspNetCore MVC project with 'Microsoft.AspNetCore.Authentication.OpenIdConnect' package.
Client initialization in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://myssoserver.local";
options.RequireHttpsMetadata = false;
HttpClientHandler handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
options.BackchannelHttpHandler = handler;
options.ClientId = "ssotestclient";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.UsePkce = true;
options.Scope.Add("email");
options.Scope.Add("profile");
options.SaveTokens = true;
});
services.AddAuthorization();
services.AddRazorPages();
services.AddControllers();
}
Logout button on the client website has the following code :
public async Task OnPostAsync(string returnUrl = null)
{
await HttpContext.SignOutAsync("Cookies");
await HttpContext.SignOutAsync("oidc");
}
After this the browser is redirected to SSO server with the following location:
https://myssoserver.local/connect/endsession?post_logout_redirect_uri=https%3A%2F%2Fmytestsite.local%2Fsignout-callback-oidc&id_token_hint=<token>&state=<state>&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0
Actual values of token and state are trimmed from this post because they are too long.
As you can see the post_logout_redirect_uri, id_token_hint and state parameters are passed to the server endsession endpoint.
And on the SSO server side I see the message that it has passed validation:
info: IdentityServer4.Validation.EndSessionRequestValidator[0]
End session request validation success
{
"ClientId": "sso_test_client",
"ClientName": "sso_test_client",
"SubjectId": "f3693d8c-6095-4f1a-9f8f-bdc7440e9395",
"PostLogOutUri": "https://mytestsite.local/signout-callback-oidc",
"State": "<state>",
"Raw": {
"post_logout_redirect_uri": "https://mytestsite.local/signout-callback-oidc",
"id_token_hint": "***REDACTED***",
"state": "<state>",
"x-client-SKU": "ID_NETSTANDARD2_0",
"x-client-ver": "5.5.0.0"
}
}
However after this request the browser receives 302 redirect to https://myssoserver.local/Identity/Account/Logout page instead of provided post_logout_redirect_uri.
And actual logout doesn't happen, because only OnGet() handler is called that does nothing
Could not find similar issue on the web.
What could be wrong? Why do I get redirected to the Logout page instead of a callback uri? I searched through the project code and could not find any reference with the 'Logout' word apart from Logout page itself
One thought is that you need to set the LogoutPath.
.AddCookie(options =>
{
options.LogoutPath = "/User/Logout";
})
LogoutPath is a security feature, just as this picture from one of my training classes shows:

Failed SignalR accentuations using Identity Server 4

I have a set up of Identity Server 4, Asp.Net Core SignalR Hub, and JavaScript client.
When I try to connect to the SignalR Hub, the "negotiateVersion: 1" pass correctly but the request to the hub doesn't pass at all. The error in Chrome is "HTTP Authentication failed; no valid credentials available" and 401 status code in FireFox. The id and the access token are present in the query. I try different examples but with no desired result.
Versions:
Identity Server 4 - .Net Core 2.1
IdentityServer4.AspNetIdentity(2.5.0)
Asp.Net Core SignalR Hub - .Net Core 3.1
IdentityServer4.AccessTokenValidation(3.0.1)
Microsoft.AspNetCore.Authentication.JwtBearer(3.1.7)
JavaScript Client
signalr.min.js v4.2.2+97478eb6
Here is my configuration in Identity Server:
Config.cs
ClientId = "my.client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
Claims = { },
RedirectUris = {
"https://mySite/popupFrame.html",
"https://mySite/silentFrame.html",
},
PostLogoutRedirectUris = {"https://mySite/logoutFrame.html" },
RequireConsent = false,
AllowAccessTokensViaBrowser = true,
AccessTokenLifetime = (int)TimeSpan.FromMinutes(10).TotalSeconds,
Startup.cs in ConfigureServices method
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>()
.AddResourceOwnerValidator<ResourceOwnerValidator>()
.AddProfileService<ProfileService>();
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AppClaimsPrincipalFactory>();
Here is my configuration in Asp.Net SignalR:
Startup.cs in ConfigureServices method
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:44341";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
options.JwtBearerEvents = new JwtBearerEvents
{
OnMessageReceived = context =>
{
StringValues queryAccessToken = context.Request.Query["access_token"];
PathString path = context.HttpContext.Request.Path;
if (path.StartsWithSegments("/brokerhub") && !string.IsNullOrEmpty(queryAccessToken))
{
context.Token = queryAccessToken;
}
return Task.CompletedTask;
},
};
});
services.AddCors();
services.AddSignalR();
services.AddControllers();
Startup.cs in Configure method
app.UseRouting();
app.UseCors(builder =>
{
builder.WithOrigins("https://mySite", "http://localhost")
.AllowAnyHeader()
.WithMethods("GET", "POST")
.AllowCredentials();
});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<BrokerHub>("/brokerhub", options =>
{
options.Transports = HttpTransportType.WebSockets;
});
});
The Hub controller
[Authorize]
public class BrokerHub : Hub
And finally the JS Client
client = new signalR.HubConnectionBuilder()
.withUrl('https://hub.com/brokerhub/', {
accessTokenFactory: () => {
return user.access_token;
}
})
.build();
Can anyone point me what I'm missing in configurations?
Maybe this issue is relevant - https://github.com/IdentityServer/IdentityServer4/issues/2349#issuecomment-394099795
Can you please try to use options.TokenRetriever instead of OnMessageReceived as shown in the link above and introduce CustomTokenRetriever?

Auth0 + Swashbuckle .Net Core 2.2. Missing claims in jwt token when using SwaggerUI

I am making a ASP.Net Core WebApi which is authentication via Auth0. I am using Swagger and SwaggerUI and trying to authenticate from Swagger UI.
// Add authentication services
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options =>
{
// Set the authority to your Auth0 domain
options.Authority = $"https://{Configuration["Auth0:Authority"]}";
// Configure the Auth0 Client ID and Client Secret
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
// Set response type to code
options.ResponseType = "code";
// Configure the scope
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("offline_access");
options.SaveTokens = true;
// Set the callback path, so Auth0 will call back to http://localhost:3000/callback
// Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard
options.CallbackPath = new PathString("/callback");
// Configure the Claims Issuer to be Auth0
options.ClaimsIssuer = "Auth0";
// Saves tokens to the AuthenticationProperties
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SetParameter("audience", #"https://predictor-dev.api");
return Task.FromResult(0);
},
// handle the logout redirection
OnRedirectToIdentityProviderForSignOut = (context) =>
{
var logoutUri = $"https://{Configuration["Auth0:Authority"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}";
var postLogoutUri = context.Properties.RedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
// transform to absolute
var request = context.Request;
postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
}
logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}";
}
context.Response.Redirect(logoutUri);
context.HandleResponse();
return Task.CompletedTask;
}
};
})
.AddJwtBearer(options =>
{
options.Authority = Configuration["Auth0:Authority"];
options.Audience = Configuration["Auth0:Audience"];
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/roles"
};
options.ClaimsIssuer = "Auth0";
});
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder =>
{
builder
.WithOrigins(Configuration["FrontendBaseUrl"])
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Predictor API", Version = "v1" });
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"{Configuration["Auth0:Authority"]}authorize?audience={Configuration["Auth0:Audience"]}",
Scopes = new Dictionary<string, string>
{
{ "read:books", "Access read book operations" },
{ "write:books", "Access write book operations" }
}
});
c.OperationFilter<SecurityRequirementsOperationFilter>();
});
Here is the token which is returned after authentication via SwaggerUI:
{
"iss": "my iss",
"sub": "my sub",
"aud": "my aud",
"iat": 1556002815,
"exp": 1556010015,
"azp": "azp",
"scope": "read:books"
}
The problem here is that token doesn't have openid and profile information.
I don't have any custom rules in Auth0 that could limit my scopes (I removed them totally).I tried different options, but I could not get any additional claims.
Is there any configuration in Swagger that I am missing?
Thank you.
You have to pass "openid" and "profile" scopes to extend your token with openid and profile information

IdentityServer4 on .Net Core2 with bearer - API not validating auth / custom policy handler

I try to setup an identity server 4 + API + web Scenario but can't get users authenticated in the api. Each component uses a separate Project within my VS solution. All Projects are on dotnetcore 2.0.
Startup.cs Identity Server
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddCors();
services.AddIdentityServer(options =>
{
options.Events.RaiseSuccessEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseErrorEvents = true;
})
.AddInMemoryClients(Clients.Get())
.AddInMemoryIdentityResources(Resources.GetIdentityResources())
.AddInMemoryApiResources(Resources.GetApiResources())
.AddDeveloperSigningCredential()
.AddExtensionGrantValidator<Extensions.ExtensionGrantValidator>()
.AddExtensionGrantValidator<Extensions.NoSubjectExtensionGrantValidator>()
.AddTestUsers(TestUsers.Users);
TestUsers.Users
public class TestUsers
{
public static List<IdentityServer4.Test.TestUser> Users = new List<IdentityServer4.Test.TestUser>
{
new IdentityServer4.Test.TestUser{SubjectId = "818727", Username = "alice", Password = "alice",
Claims =
{
new Claim(JwtClaimTypes.Role, "UserEditor"),
new Claim(JwtClaimTypes.Name, "Alice Smith"),
new Claim(JwtClaimTypes.Email, "AliceSmith#email.com"),
}
},
new IdentityServer4.Test.TestUser{SubjectId = "88421113", Username = "bob", Password = "bob",
Claims =
{
new Claim(JwtClaimTypes.Role, "Root"),
new Claim(JwtClaimTypes.Role, "Admin"),
new Claim(JwtClaimTypes.Role, "UserEditor"),
new Claim(JwtClaimTypes.Name, "Bob Smith"),
new Claim(JwtClaimTypes.Email, "BobSmith#email.com")
}
}
};
}
Getting a indentityserver jwt bearer token works via http://localhost:2266/connect/token and it contains the relevant Information:
{
...
...
"role": [
"Root",
"Admin",
"UserEditor"
],
"scope": [
...
],
...
}
However - on the API side authentication is not checked properly.
API Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext...
...
...
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:2266";
options.RequireHttpsMetadata = false;
options.ApiSecret = "secret";
options.ApiName = "MyApi";
});
services.AddCors(options =>
{
// this defines a CORS policy called "default"
options.AddPolicy("default", policy =>
{
policy.WithOrigins("http://localhost:44352")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
// custom policy attributes
services.AddAuthorization(options =>
{
options.AddPolicy("Root", policy => policy.Requirements.Add(new Models.Policies.MyPolicyRequirement("Root")));
options.AddPolicy("Admin", policy => policy.Requirements.Add(new Models.Policies.MyPolicyRequirement("Admin")));
options.AddPolicy("UserEdit", policy => policy.Requirements.Add(new Models.Policies.MyPolicyRequirement("UserEdit")));
});
services.AddSingleton<IAuthorizationHandler, Models.Policies.MyPolicyHandler>();
services.AddMvc();
// add swagger
...
}
The policy Validation
Controller/Actions are marked with the Authorize Attribute, e.g
[Authorize(Policy = "Root")]
The Code of the policy handler is hit while Debugging.
public class MyPolicyHandler : AuthorizationHandler<MyPolicyRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyPolicyRequirement requirement)
{
if ( !context.User.Identity.IsAuthenticated )
return Task.CompletedTask;
if (context.User.Identities.FirstOrDefault().HasClaim(System.Security.Claims.ClaimTypes.Role, requirement.Policy))
{
context.Succeed(requirement);
return Task.CompletedTask;
}
return Task.CompletedTask;
}
}
The validation fails because the context.User.Identity.IsAuthenticated is false, the objects identity has no Claims as well.
Looks like something is missing in the Pipeline to convert my bearer authentication to an user identity.
Any suggestions?
Finally found the problem:
I had a reference to Microsoft.IdentityModel.Tokens (5.2.0-preview1) because of
some code to load the signing certificate.
If that package is enabled the Validation Fails.
I once started with validating the signing certificate of the token by myself so i needed that component but i guess all that will be handled automatically in the AddIdentityServerAuthentication part?
So the policy check was not the Problem. Thanks for asking me to start from base.