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

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.

Related

ASP.NET Core: OpenIdConnect: message.State is null or empty

I get this error:
OpenIdConnectAuthenticationHandler: message.State is null or empty.
with the URL https://localhost:7208/home/index, but the authentication works with the url https://localhost:7208/.
Can anyone help me understand this?
enter image description here
enter image description here
This is my code:
Program.cs:
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using System.Security.Claims;
using System.Web.Mvc;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddMvc().AddSessionStateTempDataProvider();
builder.Services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
});
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
})
.AddOpenIdConnect(options =>
{
options.Authority = builder.Configuration["OpenIdConfigurations:Authority"];
options.MetadataAddress = builder.Configuration["OpenIdConfigurations:MetadataAddress"];
options.ResponseType = builder.Configuration["OpenIdConfigurations:ResponseType"];
options.GetClaimsFromUserInfoEndpoint = Convert.ToBoolean(builder.Configuration["OpenIdConfigurations:GetClaimsFromUserInfoEndpoint"]);
options.RequireHttpsMetadata = Convert.ToBoolean(builder.Configuration["OpenIdConfigurations:RequireHttpsMetadata"]);
options.ClientId = builder.Configuration["OpenIdConfigurations:ClientId"];
options.ClientSecret = builder.Configuration["OpenIdConfigurations:ClientSecret"];
options.CallbackPath = builder.Configuration["OpenIdConfigurations:CallbackPath"];
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"
);
app.Run();
Controller:
namespace OIDCMVC.Controllers
{
[Authorize]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
Note: We use a private provide and expects the call back path.
Callbackpath = "/home/index"
In my case I am using blazor (.net 6) and trying to protect hangfire with Microsoft oauth. To get the auth screen of microsoft login when locating to /hangfire. The solution to this error was as simple as removing CallbackPath from my settings:
// settings found under Azure AD -> App Registration -> Your registration
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "domain", // found under Branding and properties -> publisher domain like : ...outlook.onmicrosoft.com
"TenantId": "tenantid", // found under Overview Directory (tenant) ID GUID
"ClientId": "client_id" // found under Overview Application (client) ID GUID
//"CallbackPath": "/hangfire" REMOVED To get rid off message.State is null error
}
The setup from Program.cs:
services.AddAuthentication().AddMicrosoftIdentityWebApp(configuration.GetSection("AzureAd"));
services.AddAuthorization(options =>
{
options.AddPolicy("Hangfire", builder =>
{
builder
.AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme)
.RequireAuthenticatedUser();
});
});
services.AddHangfire(x =>
{
x.UseSqlServerStorage(connectionString);
x.UseConsole();
});
services.AddHangfireServer();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHangfireDashboard("/hangfire", new DashboardOptions()
{
Authorization = new List<IDashboardAuthorizationFilter> { new HangfireAuthorizeFilter() },
}).RequireAuthorization("Hangfire");
});
app.UseHangfireDashboard();
Authorization filter:
public class HangfireAuthorizeFilter:
IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var userIdentity = context.GetHttpContext().User.Identity;
return userIdentity is { IsAuthenticated: true };
}
}
And the app registration from Azure AD:
Under your app registration click: Authentication -> Mark "ID tokens" and enter your redirect urls, like: https://localhost:52908/hangfire
I've been integrating multiple custom policies (different flows) in a single app. Removing CallbackPath didn't work as request were coming there from multiple sources, not just the default sign in policy. We were receiving "message.State is null or empty" page, but after navigating to the base path of the app, the user was authenticated and properly logged in.
What ultimately helped was setting SkipUnrecognizedRequests property to true:
.AddOpenIdConnect(options =>
{
...
options.SkipUnrecognizedRequests = true;
});
or using appsettings:
{
...
"AzureAdB2C": {
"Instance": "",
"Domain": "",
"ClientId": "",
...
"SkipUnrecognizedRequests": true
},
...
}
According to the OpenIdConnectOptions.SkipUnrecognizedRequests documentation:
Indicates if requests to the CallbackPath may also be for other components. If enabled the handler will pass requests through that do not contain OpenIdConnect authentication responses. Disabling this and setting the CallbackPath to a dedicated endpoint may provide better error handling. This is disabled by default.
Also, maybe related issue here: OIDC handler running on '/' crashes even with SkipUnrecognizedRequests set to true #10028

Validate the JWT Bearer Token In Identity Server 4

I have an Identity Server running based on IdentityServer 4, and I have an ASP.NET WebAPI built in ASP.Net Core Web API. I have a successfully login on the /connect/token endpoint of the identity server. I want to check the validity of JWT bearer token sent in the header of my API requests.
This is the configuration in my startup API project :
In ConfigureServices :
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
//base-address of my identityserver
options.Authority = "https://localhost:5000/";
//name of the API resource
options.ApiName = "API_Resource_Name";
});
In Configure :
app.UseAuthentication();
NB : I've Added Authorize Annotation to my controller
Add authentication and authorization to your API Startup.cs ConfigureServices:
services.AddAuthentication("bearer")
.AddJwtBearer("bearer", options =>
{
options.Authority = Configuration["Authority"];
options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/chathub")))
{
context.Token = accessToken;
}
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
var token = context.SecurityToken as JwtSecurityToken;
if (token != null)
{
ClaimsIdentity identity = context.Principal.Identity as ClaimsIdentity;
if (identity != null)
{
identity.AddClaim(new Claim("access_token", token.RawData));
}
}
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
NameClaimType = "name",
RoleClaimType = "role"
};
});
And then...
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "SignalR.API");
});
});
Inside Configure...
app.UseAuthentication();
app.UseAuthorization();

How to change ".AspNetCore.Identity.Application" cookie expiration?

I'm using ASP.NET Core with Identity Server and Open Id Connect as described here. I need to change the time of authentication cookie expiration when the Remember Me option is set (14 days by default). I can see that the cookie named ".AspNetCore.Identity.Application" is responsible for that. I'm trying to set the expiration like this:
.AddCookie(options =>
{
options.Cookie.Expiration = TimeSpan.FromDays(1);
options.ExpireTimeSpan = TimeSpan.FromDays(1);
})
But it affects another cookie named ".AspNetCore.Cookies" (containing the same token value), which has Session expiration and doesn't seem to do anything. All the ways to change expiration that I found modify only the ".AspNetCore.Cookies" cookie, I couldn't find any way to modify the ".AspNetCore.Identity.Application" cookie. (By the way, the services.ConfigureApplicationCookie method isn't triggered for me at all for some reason).
Could anyone please explain what is the difference between these two cookies and how can I modify the ".AspNetCore.Identity.Application" expiration?
My code in Startup.ConfigureServices
services.AddMvc(options =>
{
// ...
})
services.AddAuthorization(options =>
{
options.AddPolicy(PolicyNames.UserPolicy, policyBuilder =>
{
// ...
});
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.AccessDeniedPath = "/AccessDenied";
options.SlidingExpiration = true;
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "<authority>";
options.RequireHttpsMetadata = false;
options.ClientId = "<id>";
options.ClientSecret = "<secret>";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
// ...
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "MyCookie";
options.Cookie.Expiration = TimeSpan.FromDays(1);
options.ExpireTimeSpan = TimeSpan.FromDays(1);
});
As Kirk Larkin said ".AspNetCore.Identity.Application" cookie is probably set by the Identity Server application that make use of Asp.Net Identity.
So if you want to manage the user session on the IS4 app you need to configure it there.
IS4 application: ".AspNetCore.Identity.Application" cookie.
If you use Identity to configure the cookie as persistent you need to set the expiration when you sign in the user.
var props = new AuthenticationProperties {
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
await HttpContext.SignInAsync(userId, userName, props);
If you don't set IsPersistent=true then the cookie has session lifetime and you can set the contained authentication ticket expiration like this:
.AddCookie(options => {
options.Cookie.Name = "idsrv_identity";
options.ExpireTimeSpan = TimeSpan.FromHours(8);
options.SlidingExpiration = true;
});
Your client application: : ".AspNetCore.Cookies" cookie.
services.ConfigureApplicationCookie isn't called because if you use .AddCookie(...) this takes the precedence. The options are the same.
This set the app cookie as session.
.AddCookie(options => {
options.Cookie.Name = "myappcookie";
options.ExpireTimeSpan = TimeSpan.FromHours(8);
options.SlidingExpiration = true;
});
A way to make the app cookie persistent using OIDC is to set the expiration in the OnSigningIn event in AddCookie.
options.Events.OnSigningIn = (context) =>
{
context.CookieOptions.Expires = DateTimeOffset.UtcNow.AddDays(30);
return Task.CompletedTask;
};
A note about user session.
Every situation is different, so there isn't a best solution, but remember that you have to take care of two user session. One on the IS4 app and one on your client app. These can go out of sync. You need to think if a persistent user session on your client app make sense. You don't want that your user remains logged in your client app when the central SSO (single sign-on) session is expired.
After scrambled through the both AspNetCore 3.1 & IdentityServer 4.0.4 repo,
I found the working way to set default authentication cookie option .
TD;LR:
// in Startup.ConfigureService(IServiceCollection services)
services.PostConfigure<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme, option =>
{
option.Cookie.Name = "Hello"; // change cookie name
option.ExpireTimeSpan = TimeSpan.FromSeconds(30); // change cookie expire time span
});
Full Setup:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
// cookie policy to deal with temporary browser incompatibilities
services.AddSameSiteCookiePolicy();
services.AddDefaultAllowAllCors();
// setting up dbcontext for stores;
services.AddDbContext<ApplicationDbContext>(ConfigureDbContext);
services
.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
// read clients from https://stackoverflow.com/a/54892390/4927172
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseSuccessEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.UserInteraction.LoginUrl = "/identity/account/login";
options.IssuerUri = _configuration.GetValue<string>("IdentityServer:IssuerUri");
})
.AddAspNetIdentity<ApplicationUser>()
.AddDeveloperSigningCredential()
.AddConfigurationStore<ApplicationConfigurationDbContext>(option => option.ConfigureDbContext = ConfigureDbContext)
.AddOperationalStore<ApplicationPersistedGrantDbContext>(option => { option.ConfigureDbContext = ConfigureDbContext; })
.AddJwtBearerClientAuthentication()
.AddProfileService<ApplicationUserProfileService>();
services.PostConfigure<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme, option =>
{
option.Cookie.Name = "Hello";
option.ExpireTimeSpan = TimeSpan.FromSeconds(30);
});
services.AddScoped<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, EmailSender>();
services.Configure<SmsOption>(_configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// use this for persisted grants store
InitializeDatabase(app);
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultAllowAllCors();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseStatusCodePages(async context =>
{
var response = context.HttpContext.Response;
if (response.StatusCode == StatusCodes.Status401Unauthorized ||
response.StatusCode == StatusCodes.Status403Forbidden)
response.Redirect("/identity/account/login");
if (context.HttpContext.Request.Method == "Get" && response.StatusCode == StatusCodes.Status404NotFound)
{
response.Redirect("/index");
}
});
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapRazorPages();
});
}
Adding this line before services.AddAuthentication is what worked for me eventually with IS4, taken from this github issue:
services.ConfigureApplicationCookie(x =>
{
x.ExpireTimeSpan = TimeSpan.FromDays(1);
});
I followed the sample AuthSamples.Cookies of the Github aspnetcore sources.
public void ConfigureServices(IServiceCollection services)
{
...
// Example of how to customize a particular instance of cookie options and
// is able to also use other services.
services.AddSingleton<IConfigureOptions<CookieAuthenticationOptions>, ConfigureMyCookie>();
}
internal class ConfigureMyCookie : IConfigureNamedOptions<CookieAuthenticationOptions>
{
// You can inject services here
public ConfigureMyCookie()
{
}
public void Configure(string name, CookieAuthenticationOptions options)
{
// Identityserver comes with two cookies:
// Identity.Application
// Identity.External
// you can change the options here
{
options.ExpireTimeSpan = TimeSpan.FromHours(8);
}
}
public void Configure(CookieAuthenticationOptions options)
=> Configure(Options.DefaultName, options);
}

Secure API with JWT access token

I am playing around with the openiddict Authorization code flow sample and all is working well.
https://github.com/openiddict/openiddict-samples/tree/dev/samples/CodeFlow
However, I want to make certain changes and I am struggling to do this. I would like to configure to use JWT tokens instead of the default opaque tokens, and also separate into an authorization server and a resource server. I also have an MCV web app that will communicate with the resource server via a httpClient.
Auth Server.Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.UseOpenIddict();
});
// Register the Identity services.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});
services.AddOpenIddict()
.AddCore(options =>
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})
// Register the OpenIddict server handler.
.AddServer(options =>
{
options.UseMvc();
options.EnableAuthorizationEndpoint("/connect/authorize")
.EnableLogoutEndpoint("/connect/logout")
.EnableTokenEndpoint("/connect/token")
.EnableUserinfoEndpoint("/api/userinfo");
options.RegisterScopes(OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles);
options.AllowAuthorizationCodeFlow();
options.EnableRequestCaching();
options.DisableHttpsRequirement();
options.UseJsonWebTokens();
options.AddEphemeralSigningKey();
});
}
As this is no longer a resource server I have removed the validation parts as I don't think this is required. And as I want to use JWT I have un-commented the following lines:
options.UseJsonWebTokens();
options.AddEphemeralSigningKey();
The authorization endpoint returns a SignIn result exactly like the sample, which redirects to the MVC app which then issues an authentication cookie. I can now access protected resources on my MVC APP.
MVC APP startup
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PortalDetails>(options => Configuration.GetSection("PortalDetails").Bind(options));
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(opts =>
{
opts.LoginPath = "/login";
opts.LogoutPath = "/logout";
})
.AddJwtBearer(options =>
{
//Authority must be a url. It does not have a default value.
options.Authority = "http://localhost:54540/";
options.Audience = "mvc"; //This must be included in ticket creation
options.RequireHttpsMetadata = false;
options.IncludeErrorDetails = true; //
options.TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "sub",
RoleClaimType = "role"
};
})
.AddOpenIdConnect(options =>
{
// Note: these settings must match the application details
// inserted in the database at the server level.
options.ClientId = "mvc";
options.ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654";
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = false; // TODO: If this if true then it doesnt work??
options.SaveTokens = true;
// Use the authorization code flow.
options.ResponseType = OpenIdConnectResponseType.Code;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
// Note: setting the Authority allows the OIDC client middleware to automatically
// retrieve the identity provider's configuration and spare you from setting
// the different endpoints URIs or the token validation parameters explicitly.
options.Authority = "http://localhost:54540/";
options.Scope.Add("email");
options.Scope.Add("roles");
options.SecurityTokenValidator = new JwtSecurityTokenHandler
{
// Disable the built-in JWT claims mapping feature.,
InboundClaimTypeMap = new Dictionary<string, string>()
};
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "role";
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddHttpClient<IApiGatewayClient, ApiGatewayClient>();
services.AddSingleton<ITokenProvider, TokenProvider>();
}
When calling the resource server I use:
string accessToken = await HttpContext.GetTokenAsync("access_token");
and I can see an access token, I attach that to my http request:
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
but the result is forbidden.
Finally, I have a protected resource server:
Resource.Startup
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
//Add authentication and set default authentication scheme
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) //same as "Bearer"
.AddJwtBearer(options =>
{
//Authority must be a url. It does not have a default value.
options.Authority = "http://localhost:54540";
options.Audience = "mvc"; //This must be included in ticket creation
options.RequireHttpsMetadata = false;
options.IncludeErrorDetails = true; //
options.TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = OpenIdConnectConstants.Claims.Subject,
RoleClaimType = OpenIdConnectConstants.Claims.Role,
};
});
services.AddMvc();
}
I would like to know if this is the correct setup for my scenario, as I am getting a forbidden result from my resource server.
Thanks
Here is a package which
Makes integrating JWT Bearer Token Security in your Asp Net Core 2.0+ app a breeze!
Azure Active Directory auth integration.
Facebook auth integration.
Twitter auth integration.
Google auth integration.
Also, Swagger UI integration!
It is called AspNetCore.Security.Jwt
GitHub:
https://github.com/VeritasSoftware/AspNetCore.Security.Jwt
The package integrates JWT bearer token into your app as below:
1. Implement IAuthentication interface in your app
using AspNetCore.Security.Jwt;
using System.Threading.Tasks;
namespace XXX.API
{
public class Authenticator : IAuthentication
{
public async Task<bool> IsValidUser(string id, string password)
{
//Put your id authenication here.
return true;
}
}
}
2. In your Startup.cs
using AspNetCore.Security.Jwt;
using Swashbuckle.AspNetCore.Swagger;
.
.
public void ConfigureServices(IServiceCollection services)
{
.
.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "XXX API", Version = "v1" });
});
services.AddSecurity<Authenticator>(this.Configuration, true);
services.AddMvc().AddSecurity();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
.
.
.
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "XXX API V1");
});
app.UseSecurity(true);
app.UseMvc();
}
3. In your appsettings.json
Note:- You can put these settings in Secret Manager by using Manage User Secrets menu (right-click your Project).
{
"SecuritySettings": {
"Secret": "a secret that needs to be at least 16 characters long",
"Issuer": "your app",
"Audience": "the client of your app",
"IdType": "Name",
"TokenExpiryInHours" : 2
},
.
.
.
}
Then you will get endpoints automatically:
/token
/facebook
When you call these endpoints and are successfully authenticated, you will get back a JWT Bearer Token.
In your Controller that you want to secure
You must mark the Controller or Action that you want to secure with Authorize attribute like:
using Microsoft.AspNetCore.Mvc;
.
.
.
namespace XXX.API.Controllers
{
using Microsoft.AspNetCore.Authorization;
[Authorize]
[Route("api/[controller]")]
public class XXXController : Controller
{
.
.
.
}
}
In Swagger UI, you will automatically see these endpoints.

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

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";
});