I'm trying to implement the Okta.AspNetCore middleware based on their docs on their GitHub page https://github.com/okta/okta-aspnet/blob/master/docs/aspnetcore-webapi.md but to no avail.
This is the stack trace:
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[3]
Exception occurred while processing message.
System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://company.oktapreview.com/oauth2/aus17ct73zmZrZjbh0h8/.well-known/openid-configuration'.
at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
Here is my config in program.cs
using Lamar;
using Lamar.Microsoft.DependencyInjection;
using Microsoft.IdentityModel.Logging;
using Microsoft.OpenApi.Models;
using NCL.StockSupplyChain.Common.Configuration;
using NCL.StockSupplyChain.PurchaseOrder.Web.API;
using NCL.StockSupplyChain.PurchaseOrder.Web.API.Classes;
using NCL.StockSupplyChain.ServiceInventory.Token;
using NCL.StockSupplyChain.ServiceInventory.Workflow;
using Okta.AspNetCore;
var corsPolicyName = "EnableCors";
var builder = WebApplication.CreateBuilder(args);
var logger = builder.Logging.Services.BuildServiceProvider().GetRequiredService<ILogger<Program>>();
IdentityModelEventSource.ShowPII = true;
// use Lamar as DI.
builder.Host.UseLamar()
.ConfigureContainer<ServiceRegistry>(builder =>
{
builder.Scan(x =>
{
x.AssembliesFromApplicationBaseDirectory(filter => filter.FullName is not null && filter.FullName.StartsWith("NCL."));
x.TheCallingAssembly();
x.RegisterConcreteTypesAgainstTheFirstInterface();
});
});
builder.Services.AddControllers()
.AddJsonOptions(jsonOptions => jsonOptions.JsonSerializerOptions.PropertyNamingPolicy = null);
builder.Services
.AddRouting(options => options.LowercaseUrls = true)
.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "NCL.StockSupplyChain.PurchaseOrder.Web.API", Version = "v1" });
})
.AddCors(options =>
{
options.AddPolicy(name: corsPolicyName,
corsBuilder =>
{
var allowedOrigins = builder.Configuration.Get<Configuration>().Cors?.AllowedOrigins;
if (allowedOrigins?.Any() ?? false)
{
corsBuilder.WithOrigins(allowedOrigins.ToArray())
.AllowAnyHeader()
.AllowAnyMethod();
}
});
})
.AddHttpClient()
.Configure<Configuration>(builder.Configuration)
.Configure<ConnectionStringsConfiguration>(builder.Configuration.GetSection("ConnectionStrings"))
.Configure<WorkflowConfiguration>(builder.Configuration.GetSection("WorkflowService"))
.Configure<TokenConfiguration>(builder.Configuration.GetSection("TokenService"))
.AddLogging()
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme;
options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme;
options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme;
})
.AddOktaWebApi(new OktaWebApiOptions()
{
OktaDomain = "https://company.oktapreview.com",
AuthorizationServerId = "aus17ct73zmZrZjbh0h8",
BackchannelHttpClientHandler = new MyLoggingHandler(logger),
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage()
.UseSwagger()
.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "NCL.StockSupplyChain.PurchaseOrder.Web.API"));
}
app.UseHttpsRedirection()
.UseRouting()
.UseAuthorization()
.UseCors(corsPolicyName)
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
await app.RunAsync();
Does anyone know what I could be missing? This has left me scratching my head.
In the end I got it working with this code:
using Lamar;
using Lamar.Microsoft.DependencyInjection;
using Microsoft.IdentityModel.Logging;
using Microsoft.OpenApi.Models;
using NCL.StockSupplyChain.Common.Configuration;
using NCL.StockSupplyChain.PurchaseOrder.Web.API.Classes;
using NCL.StockSupplyChain.ServiceInventory.Token;
using NCL.StockSupplyChain.ServiceInventory.Workflow;
using Okta.AspNetCore;
var corsPolicyName = "EnableCors";
var builder = WebApplication.CreateBuilder(args);
var logger = builder.Logging.Services.BuildServiceProvider().GetRequiredService<ILogger<Program>>();
// use Lamar as DI.
builder.Host.UseLamar()
.ConfigureContainer<ServiceRegistry>(builder =>
{
builder.Scan(x =>
{
x.AssembliesFromApplicationBaseDirectory(filter => filter.FullName is not null && filter.FullName.StartsWith("NCL."));
x.TheCallingAssembly();
x.RegisterConcreteTypesAgainstTheFirstInterface();
});
});
builder.Services.AddControllers()
.AddJsonOptions(jsonOptions => jsonOptions.JsonSerializerOptions.PropertyNamingPolicy = null);
builder.Services
.AddRouting(options => options.LowercaseUrls = true)
.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "NCL.StockSupplyChain.PurchaseOrder.Web.API", Version = "v1" });
})
.AddCors(options =>
{
options.AddPolicy(name: corsPolicyName,
corsBuilder =>
{
var allowedOrigins = builder.Configuration.Get<Configuration>().Cors?.AllowedOrigins;
if (allowedOrigins?.Any() ?? false)
{
corsBuilder.WithOrigins(allowedOrigins.ToArray())
.AllowAnyHeader()
.AllowAnyMethod();
}
});
})
.AddHttpClient()
.Configure<Configuration>(builder.Configuration)
.Configure<ConnectionStringsConfiguration>(builder.Configuration.GetSection("ConnectionStrings"))
.Configure<WorkflowConfiguration>(builder.Configuration.GetSection("WorkflowService"))
.Configure<TokenConfiguration>(builder.Configuration.GetSection("TokenService"))
.AddLogging()
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme;
options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme;
options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme;
})
.AddOktaWebApi(new OktaWebApiOptions()
{
OktaDomain = "https://org.oktapreview.com",
AuthorizationServerId = "aus17ct73zmZrZjbh0h8",
Audience = "0oa173fmolqChJbj00h8"
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage()
.UseSwagger()
.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "NCL.StockSupplyChain.PurchaseOrder.Web.API"));
// This line enables more detailed stacktrace for auth code errors, Idendity framework, auth middleware etc
IdentityModelEventSource.ShowPII = true;
}
app.UseHttpsRedirection()
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseCors(corsPolicyName)
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
await app.RunAsync();
These two lines of code have to be in this order otherwise it will error:
.UseAuthentication()
.UseAuthorization()
Hopefully this helps someone who gets stuck with the error: System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://org.oktapreview.com/oauth2/aus17ct73zmZrZjbh0h8/.well-known/openid-configuration'. in the future because I thought I had configured Okta incorrectly.
Related
How to configure IIS from a webapi that was in net3.1 in net7.0
?
I have an application in net7 that should run on the server on top of iis, but I don't know how to configure it in net7, before there was a solution made in 3.1
What I have today:
var assembly = Assembly.GetExecutingAssembly()
?? throw new InvalidOperationException("It was not possible to get executing assembly information");
var options = new WebApplicationOptions()
{
ContentRootPath = AppContext.BaseDirectory,
Args = args,
ApplicationName = assembly.GetName().Name
};
var builder = WebApplication.CreateBuilder(options);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
builder.Host.UseWindowsService();
}
builder.Configuration.AddJsonFile("hostsettings.json", true);
builder.Configuration
.SetBasePath(builder.Environment.ContentRootPath)
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", true, true)
.AddEnvironmentVariables();
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
policy =>
{
policy.AllowAnyOrigin();
policy.AllowAnyHeader();
policy.AllowAnyMethod();
});
});
builder.Services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddLogger();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors();
app.MapControllers();
app.Run();
I have IdentityServer4 (IDS4) working with multiple apps (Blazor WASM, ASP.NET Core API) using authorization_code. I'm using ASP.NET Identity for all apps.
I added one more API controller to Identityserver4 app (Controllers/Api/AccountController) and added one client for client_credentials flow. Purpose of this client is to access AccountController from API projects with authorization.
When I call IDS/api/Account/Register, it redirects to login page. Without [Authorize] attribute, it works fine.
How can I configure authorization for a client_credentials flow client?
Here are my IDS configs:
var services = builder.Services;
var configuration = builder.Configuration;
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
//options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Lax;
});
builder.Services.AddControllersWithViews()
.AddSessionStateTempDataProvider()
.AddRazorPagesOptions(options =>
{
//options.AllowAreas = true;
//options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
});
var connectionString = configuration.GetConnectionString("MSSQLConnection");
var applicationName = builder.Environment.ApplicationName;
services.AddDbContext<AppDbContext>(builder => builder.UseSqlServer(connectionString,
options => options.MigrationsAssembly(applicationName)));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
//.AddDefaultUI();
services.AddLogging(options =>
{
options.AddConsole();
});
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
// ..or configures IIS out-of-proc settings
services.Configure<IISOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
// ..or configures IIS in-proc settings
services.Configure<IISServerOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseSuccessEvents = true;
options.Discovery.ShowIdentityScopes = false;
options.Discovery.ShowApiScopes = false;
options.Discovery.ShowClaims = false;
options.Discovery.ShowExtensionGrantTypes = false;
options.UserInteraction.LoginUrl = "/Account/Login";
options.UserInteraction.LogoutUrl = "/Account/Logout";
options.Authentication = new IdentityServer4.Configuration.AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromHours(10), // ID server cookie timeout set to 10 hours
CookieSlidingExpiration = true
};
})
.AddOperationalStore(options => options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(applicationName)))
.AddConfigurationStore(options => options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(applicationName)))
.AddAspNetIdentity<ApplicationUser>()
.AddInMemoryCaching()
.AddResourceOwnerValidator<ResourceOwnerPasswordValidatorService>()
.AddSigningCredential(certificate);
services.AddAuthentication();
services.AddLocalApiAuthentication();
services.AddAuthorization(options =>
{
options.AddPolicy(Roles.Admin,
policy => policy.RequireClaim(JwtClaimTypes.Role, Roles.GetRoleName(UserRole.Admin)));
options.AddPolicy(Roles.SuperAdmin, policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(JwtClaimTypes.Role, Roles.SuperAdmin) || context.User.HasClaim(JwtClaimTypes.Role, Roles.Admin)));
});
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(10);
});
Update (Resolution)
Finally resolved using Bearer AuthenticationScheme support to IDS for Api Endpoints
In Program.cs
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = IdentityServerUtility.Authority;
options.ApiName = "IDS_API";
});
In Controller Authorization including policy works fine.
[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme)]
public class AccountController : ControllerBase
{
[Authorize(Policy=Roles.Admin)]
[HttpPost("Register")]
public async Task<AppActionResult> Register([FromBody] UserDto model)
{
//...
}
}
Thank you.
We're trying to develop an ASP Net application, and we have to use an oidc authentication system.
we've got our own OIDC server. When we try to connect we've got an error message :
"System.Exception: An error was encountered while handling the remote login.
---> Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolException: Message contains error: 'invalid_client', error_description: 'Invalid authentication method for accessing this endpoint.', error_uri: 'error_uri is null'."
here is our ConfigureService function and Configure function:
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
services.AddMvc();
services.AddOptions();
SetGlobalConfig();
services.AddHttpContextAccessor();
services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.EnableForHttps = true;
});
services.AddAntiforgery(options =>
{
options.Cookie.Name = "X-CSRF-TOKEN-OurAppli";
options.HeaderName = "X-CSRF-TOKEN-OurAppli";
options.FormFieldName = "X-CSRF-TOKEN-OurAppli";
});
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(5);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
});
services.AddDetection();
services.AddControllersWithViews();
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
var bearerUrl = Configuration.GetValue<string>("BearerUrl");
var callBackUrl = Configuration.GetValue<string>("CallBackUrl");
var sessionCookieLifetime = Configuration.GetValue("SessionCookieLifetimeMinutes", 60);
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(setup => setup.ExpireTimeSpan = TimeSpan.FromMinutes(sessionCookieLifetime))
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.ResponseType = "code";
options.ClientId = "*******";
options.ClientSecret = "********";
options.Authority = "https://ourOIDCServer/ourOIDCServerwebsso/oauth2/multiauth";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Clear();
options.Scope.Add("openid uid isMemberOf");
options.SaveTokens = true;
options.Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://ourOIDCServer/ourOIDCServerwebsso/oauth2/multiauth/authorize",
TokenEndpoint = "https://ourOIDCServer/ourOIDCServerwebsso/oauth2/multiauth/access_token",
UserInfoEndpoint = "https://ourOIDCServer/ourOIDCServerwebsso/oauth2/multiauth/userinfo",
EndSessionEndpoint = "https://ourOIDCServer/ourOIDCServerwebsso/oauth2/multiauth/connect/endSession",
RegistrationEndpoint = "https://ourOIDCServer/ourOIDCServer/oauth2/multiauth/connect/register",
JwksUri = "ourOIDCServer/ourOIDCServer/oauth2/multiauth/connect/jwk_uri",
};
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SetParameter("acr_values","ourACR");
context.ProtocolMessage.SetParameter("authlevel", "3");
var byteArray = Encoding.ASCII.GetBytes(options.ClientId + ":" + options.ClientSecret);
context.Request.Headers.Add("Authorization", "Post " + byteArray);
return Task.FromResult(0);
}
};
});
services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(365);
});
}
public void Configure(IApplicationBuilder app, IHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseResponseCompression();
app.UseDetection();
app.UseSession();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; font-src 'self';script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline'");
await next();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "Index",
pattern: "{controller=Home}/{action=Index}");
});
app.UseStaticFiles();
}`
And there is our HomeController :
[Authorize]
public ActionResult Index()
{
Console.WriteLine(_user);
Console.WriteLine(_habilitation);
dynamic models = new ExpandoObject();
models.detection = _detectionService;
if (!string.IsNullOrEmpty(_user) || _user != "")
{
models.Authorization = _habilitation;
models.Name = _user;
Console.WriteLine(models.Name + " Auth= " + models.Authorization);
models.Habilitation = 1;
}
return View("Index", models);
}
I never used an OIDC authentication system with ASP Net so i don't know if there is something wrong with my code...
Hope someone can help me!
Can you add to the question how the client is defined? What OIDC provider are you using?
The problem seems that the client asks for "authorization code flow" here:
options.ResponseType = "code";
but the OIDC provider does not have that enabled. I would check that first.
Why i try to access a static file in my dot net core backend i get
Access to XMLHttpRequest at 'https://localhost:5001/uploads/132248599151771104.jpg' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
And this message in the .net console
CORS policy execution successful.
i've tried
services.AddCors (options => {
options.AddPolicy ("CorsPolicy", builder => {
builder
.AllowAnyOrigin()
.AllowAnyMethod ()
.AllowAnyOrigin()
.AllowAnyHeader ();
});
});
and
app.UseStaticFiles ();
app.UseStaticFiles (new StaticFileOptions {
OnPrepareResponse = ctx => {
ctx.Context.Response.Headers.Append(new KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues>("Access-Control-Allow-Origin", "*"));
ctx.Context.Response.Headers.Append(new KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues>("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"));
},
FileProvider = new PhysicalFileProvider (
Path.Combine (Directory.GetCurrentDirectory (), "Uploads")),
RequestPath = new Microsoft.AspNetCore.Http.PathString ("/Uploads")
});
Startup.cs:
public void ConfigureServices (IServiceCollection services) {
services.AddMvc ().SetCompatibilityVersion (CompatibilityVersion.Version_2_2);
var key = Encoding.ASCII.GetBytes ("this is the secret phrase");
services.AddAuthentication (x => {
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer (x => {
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey (key),
ValidateIssuer = false,
ValidateAudience = false
};
});
//Enable Cross-Origin Resource Sharing (Front-end and backend on the same server)
services.AddCors (options => {
options.AddPolicy ("CorsPolicy", builder => {
builder
.AllowAnyOrigin()
.AllowAnyMethod ()
.AllowAnyOrigin()
.AllowAnyHeader ();
});
});
services.AddControllers ().AddNewtonsoftJson (options => {
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
//
services.AddDbContext<DataBaseContext> (options => {
options.UseSqlServer (Configuration.GetConnectionString ("db0"));
});
services.AddControllers ();
services.AddDbContext<RailOpsContext> (options =>
options.UseSqlServer (Configuration.GetConnectionString ("RailOpsContext")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure (IApplicationBuilder app, IWebHostEnvironment env) {
app.UseDefaultFiles ();
app.UseStaticFiles ();
app.UseStaticFiles (new StaticFileOptions {
OnPrepareResponse = ctx => {
ctx.Context.Response.Headers.Append(new KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues>("Access-Control-Allow-Origin", "*"));
ctx.Context.Response.Headers.Append(new KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues>("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"));
},
FileProvider = new PhysicalFileProvider (
Path.Combine (Directory.GetCurrentDirectory (), "Uploads")),
RequestPath = new Microsoft.AspNetCore.Http.PathString ("/Uploads")
});
// app.UseDirectoryBrowser (new DirectoryBrowserOptions () {
// FileProvider = new PhysicalFileProvider (
// Path.Combine (Directory.GetCurrentDirectory (), #"Uploads")),
// RequestPath = new Microsoft.AspNetCore.Http.PathString ("/Uploads")
// });
app.UseCors ("CorsPolicy");
if (env.IsDevelopment ()) {
app.UseDeveloperExceptionPage ();
}
app.UseHttpsRedirection ();
app.UseRouting ();
app.UseAuthentication ();
app.UseAuthorization ();
app.UseEndpoints (endpoints => {
endpoints.MapControllers ();
});
}
You need to put app.UseCors ("CorsPolicy"); as the first line in Configure method.
I am trying to use asp.net identity framework for mvc and JWT for APIs. Requirement is that api accessing username/device is in the url, for example, api/v1/username/accounts. The user or the device that JWT was issues has username in it. Can I do it in the startup.cs file. The following code was working fine until recently then it started doing strange thing by allowing asp.net identity to use JWT protected APIs. I want to check if username in the url api/v1/username/accounts matches the token one .Following is my code. Thanks for your insights.
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
Log.Logger = new LoggerConfiguration()
.MinimumLevel
.Warning()
.WriteTo.RollingFile("Logs/GateKeeperLog-{Date}.txt")
.CreateLogger();
}
public static IConfigurationRoot Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(Configuration);
services.AddDbContext<GkEnterpriseContext>(options =>
options.UseSqlServer(Configuration["Database:Connection"]));
services.AddTransient<IUnitOfWork, UnitOfWork>();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<GkEnterpriseContext>()
.AddDefaultTokenProviders();
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.Formatting = Formatting.Indented;
}).AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddSerilog();
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseWhen(context => context.Request.Path.Value.Contains("/api")
, builder =>
{
builder.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Audidence"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes
(JwtTokenIssuer.PrivateKey)),
ValidateLifetime = true,
NameClaimType = JwtRegisteredClaimNames.FamilyName
}
});
app.UseWhen(context => context.Request.Path.Value.StartsWith("/api/v2/computers/")
, builder1 =>
builder1.MapWhen((ctx) =>
{
var deviceName = ctx.User.Claims.SingleOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Name)?.Value ?? "";
var testPath = new Microsoft.AspNetCore.Http.PathString($"/api/v2/computers/{deviceName}");
var pathMatch = ctx.Request.Path.StartsWithSegments(testPath);
return String.IsNullOrWhiteSpace(deviceName) || !pathMatch;
}, cfg =>
{
cfg.Run((req) =>
{
req.Response.StatusCode = 403;
return req.Response.WriteAsync("Sorry , you cant access this resource...");
});
}));
});
app.UseIdentity();
app.UseStatusCodePagesWithReExecute("/StatusCodes/{0}");
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "defaultApi",
template: "api/v2/{controller}/{id?}");
});
}
}
// JWT issung code block, it is now issuing tokens as expected, only validating is the problem.
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub,computer),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.FamilyName,"GkDevice")
};
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(PrivateKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: Startup.Configuration["Tokens:Issuer"],
audience: Startup.Configuration["Tokens:Audidence"],
claims: claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddYears(10),
signingCredentials: creds
);
var data = new Token
{
Message = "New Token was issued",
Jwt = new JwtSecurityTokenHandler().WriteToken(token),
Iat = GkHelpers.ConvertTimeToEpoch(token.ValidFrom) ,
Exp = GkHelpers.ConvertTimeToEpoch(token.ValidTo)
};
return data;
Something like this might help you --
app.UseWhen(context => context.Request.Path.Value.StartsWith("/api"), builder =>
{
...jwt code...
builder.MapWhen((ctx) =>
{
var userName = ctx.User.Claims.SingleOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Name)?.Value ?? "";
var testPath = new Microsoft.AspNetCore.Http.PathString($"/api/v2/computers/{userName}/");
var pathMatch = ctx.Request.Path.StartsWithSegments(testPath);
return String.IsNullOrWhiteSpace(userName) || !pathMatch;
}, cfg =>
{
cfg.Run((req) =>
{
req.Response.StatusCode = 403;
return req.Response.WriteAsync("");
});
});
});
The inner MapWhen will trigger when the username in the "Name" claim (configure how to get the username here) does not match the given Path. It will then immediately execute the following request pipeline which will return a 403 code with an empty response body.
I am unsure, however, if you can process Identity-related items in the same request pipeline in which you actually add identity. You might have to extract that MapWhen code outside of the UseWhen.