I'm trying to use firebase email authentication on my .net core web-backend via JWT. I couldn't find detailed and clear example.
I get successful login on my android app then i get the IdToken.
I'm sending IdToken with prefix "Bearer" on PostMan(or app) to my Controller. But it gives 401
Whatever i tried i couldn't get the 200. Only 401.
My service Configuration:
services
.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://securetoken.google.com/myapp-c1e32";
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://securetoken.google.com/myapp-c1e32",
ValidateAudience = true,
ValidAudience = "myapp-c1e32",
ValidateLifetime = true
};
});
My Controller:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public ActionResult GetAllRoomsWithOtherInfos(int id)
{
var rooms = _roomService.GetAllRoomsWithOtherInfos(id);
return Ok(rooms);
}
My Request:
http://localhost:5000/Room/GetAllRoomsWithOtherInfos/1
Exception Message On Log:
Bearer was not authenticated. Failure message: IDX10501: Signature
validation failed. Unable to match keys:
Also my app and service configurations
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<TaraftarServerContext>(options => options.UseMySql(Configuration.GetConnectionString(GlobalVariables.DbConnectionName)));
services.AddMvc(options =>
{
options.Filters.Add<GlobalExceptionFilter>();
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver())
.AddFluentValidation();
services.AddKendo();
services.AddSignalR(hubOptions =>
{
hubOptions.EnableDetailedErrors = true;
})
.AddJsonProtocol(jsonOptions =>
{
jsonOptions.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver();
jsonOptions.PayloadSerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
services.AddWebClientServices();
// configure strongly typed settings objects
var appSettingsSection = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
// this method is above configuration where the jwt is ( My service Configuration:)
services.AddFirebaseAuthentication(Configuration);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
var supportedCultures = new[]
{
new CultureInfo("tr")
};
app.UseRequestLocalization(
new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("tr"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures,
FallBackToParentCultures = true,
FallBackToParentUICultures = true,
RequestCultureProviders = null
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Room}/{action=Index}/{id?}");
});
}
.Net Core Version : 2.2
Related
Sometimes google and microsoft authentication returns 500 on IdentityServer4.
Specifically, /signin-google and /signin-microsoft are returning 500 errors, and the situation is as follows
・About 3,000 requests per minute.
・About 80 of them return 500 errors.
・The rest return 301.
Do you know the cause?
Is there a problem with the number of accesses or the external authentication side?
Also, will upgrading to IdenityServer 6 and registering a license solve the problem?
Here is the code
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
AppSettings.Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer(options =>
{
options.Caching.ClientStoreExpiration = TimeSpan.FromMinutes(60);
options.Caching.ResourceStoreExpiration = TimeSpan.FromMinutes(60);
options.Caching.CorsExpiration = TimeSpan.FromMinutes(60);
})
.AddRedirectUriValidator<RedirectUriValidator>()
.AddInMemoryCaching()
.AddInMemoryIdentityResources(new IdentityResource[]
{
new IdentityResources.OpenId(), // OIDC認証を使用
new IdentityResources.Profile(),
})
.AddInMemoryApiScopes(new ApiScope[]
{
new ApiScope(IdentityServerConstants.LocalApi.ScopeName),
})
.AddInMemoryPersistedGrants()
.AddInMemoryCaching()
.AddInMemoryClients(Configuration.GetSection("Clients"))
.AddDeveloperSigningCredential()
.AddAspNetIdentity<User>();
services.ConfigureApplicationCookie(config =>
{
config.LoginPath = "/Web/User/Login";
config.LogoutPath = "/Web/User/Logout";
config.ExpireTimeSpan = TimeSpan.FromSeconds(Common.LoginTimeoutSeconds);
});
services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Common.TokenCreateKey)),
ClockSkew = TimeSpan.Zero,
};
}).AddGoogle(options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = Configuration.GetValue<string>("GoogleClient:ClientId");
options.ClientSecret = Configuration.GetValue<string>("GoogleClient:ClientSecret");
}).AddMicrosoftAccount(options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = Configuration.GetValue<string>("MicrosoftClient:ClientId");
options.ClientSecret = Configuration.GetValue<string>("MicrosoftClient:ClientSecret");
});
services.AddLocalApiAuthentication();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseAuthentication();
app.UseIdentityServer();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
One problem that I see is that you have these in the wrong order:
app.UseAuthorization();
app.UseAuthentication();
app.UseIdentityServer();
Authorization should always be last:
app.UseIdentityServer();
app.UseAuthorization();
If I am not wrong, you can also remove UseAuthentication() because UseIdentityServer adds that for you.
I have a problem , jwt authentication return 401 Error.
Token was created but always return 401 error.
I used layered architecture. I tried many things on startup.cs. JWT and startup.cs code in below. Will be updated if you want to look at another code.
JwtHelper Code:
public class JwtHelper : ITokenHelper
{
public IConfiguration Configuration { get; }
private TokenOptions _tokenOptions;
private DateTime _accessTokenExpiration;
public JwtHelper(IConfiguration configuration)
{
Configuration = configuration;
_tokenOptions = Configuration.GetSection("TokenOptions").Get<TokenOptions>();
}
public AccessToken CreateToken(User user, List<OperationClaim> operationClaims)
{
_accessTokenExpiration = DateTime.Now.AddMinutes(_tokenOptions.AccessTokenExpiration);
var securityKey = SecurityKeyHelper.CreateSecurityKey(_tokenOptions.SecurityKey);
var signingCredentials = SigningCredentialsHelper.CreateSigningCredentials(securityKey);
var jwt = CreateJwtSecurityToken(_tokenOptions, user, signingCredentials, operationClaims);
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
var token = jwtSecurityTokenHandler.WriteToken(jwt);
return new AccessToken
{
Token = token,
Expiration = _accessTokenExpiration
};
}
public JwtSecurityToken CreateJwtSecurityToken(TokenOptions tokenOptions, User user,
SigningCredentials signingCredentials, List<OperationClaim> operationClaims)
{
var jwt = new JwtSecurityToken(
issuer: tokenOptions.Issuer,
audience: tokenOptions.Audience,
expires: _accessTokenExpiration,
notBefore: DateTime.Now,
claims: SetClaims(user, operationClaims),
signingCredentials: signingCredentials
);
return jwt;
}
private IEnumerable<Claim> SetClaims(User user, List<OperationClaim> operationClaims)
{
var claims = new List<Claim>();
claims.AddNameIdentifier(user.id.ToString());
claims.AddEmail(user.Email);
claims.AddName($"{user.FirstName} {user.LastName}");
claims.AddRoles(operationClaims.Select(c => c.name).ToArray());
return claims;
}
}
Statup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
services.AddControllersWithViews();
services.AddRazorPages();
var tokenOptions = Configuration.GetSection("TokenOptions").Get<TokenOptions>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidIssuer = tokenOptions.Issuer,
ValidAudience = tokenOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = SecurityKeyHelper.CreateSecurityKey(tokenOptions.SecurityKey)
};
});
services.AddControllersWithViews();
services.AddDependencyResolvers(new ICoreModule[] {
new CoreModule()
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o => {
o.LoginPath = "/Auth/Login";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment 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.UseCors(builder => builder.WithOrigins("https://localhost:44378").AllowAnyHeader());
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Customer}/{action=Index}/{id?}");
});
app.UseStatusCodePages();
}
I have been searching for 1 week but I did not found.
After when you register JwtToken handler, this one
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o => {
o.LoginPath = "/Auth/Login";
Make your default Authentication Scheme be came CookieAuthenticationDefaults.AuthenticationScheme, which use cookie as validation.
The code should be
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme, opts =>
{
opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidIssuer = tokenOptions.Issuer,
ValidAudience = tokenOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = SecurityKeyHelper.CreateSecurityKey(tokenOptions.SecurityKey)
};
})
.AddCookie(o => {
o.LoginPath = "/Auth/Login";
});;
And as #Camilo Terevinto mention, app.UseAuthentication must come before app.UseAuthorization.
I am basically following the instruction on this youtube video https://www.youtube.com/watch?v=s2zJ_g-iQvg&ab_channel=CodAffection my problem is that I am using ASP.NET CORE while he is using ASP.NET CORE 2.2. And for some reason my api calls allways return 401 despite me having change only some line of code to make it compatible with ASP.NET CORE 2.2.
I want to make API calls when the user is login I am using ASP.NET CORE 3.1. I have made a login function which works. When I make an API call with postman the login function returns a token. However when I call another route (clients) and provided the bearer token I get a 401 error. I have searched for days but am unable to solve the problem. I have tried many different tutorials but I keep having the same problem. Thank you for your help.
[HttpPost, Route("login")]
public async Task<IActionResult> Login([FromBody] LoginModel user)
{
var userFromDb = await _userManager.FindByNameAsync(user.UserName);
if (user != null && true)//await _userManager.CheckPasswordAsync(user, user.Password))
{
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim("UserID", "1")
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superlongKeyWithALotOfWordsToMakeItMoreSecureWhichIsGoodThankYouForReadingMySecretKey#45")), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return Ok(new { token });
}
else
{
return Unauthorized();
}
}
Here is what my startup file looks like
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<ApplicationSettings>(Configuration.GetSection("AppSettings"));
services.AddMvc();
services.AddTransient<DatabaseMigrator>();
services.AddDbContext<erp_colombiaDbContext>(options => options.UseMySql(
Configuration.GetConnectionString("DefaultConnection"),
optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(DesignTimeDbContextFactory).Assembly.FullName)));
services.TryAddScoped<UserManager<Employee>>();
services.TryAddScoped<SignInManager<Employee>>();
services.AddIdentityCore<Employee>(options =>
options.SignIn.RequireConfirmedAccount = false
).AddEntityFrameworkStores<erp_colombiaDbContext>();
//Jwt Authentication
var key = Encoding.UTF8.GetBytes("superlongKeyWithALotOfWordsToMakeItMoreSecureWhichIsGoodThankYouForReadingMySecretKey#45");//Configuration["AppSettings:Secret"].ToString());
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 4;
});
services.AddCors();
//Jwt Authentication
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
};
});
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/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();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
// global cors policy
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
}
and here is part of my appSettings.json setings.
"AppSettings": {
"Secret": "superlongKeyWithALotOfWordsToMakeItMoreSecureWhichIsGoodThankYouForReadingMySecretKey#45",
"ClientURL": "https://localhost:44344"
},
Here is the other route (clients)
// GET api/clients
[HttpGet]
[Authorize]
public IEnumerable<ClientViewModel> Get()
{
ClientViewModel clientViewModel;
List<ClientViewModel> listClientViewModels = new List<ClientViewModel>();
var clients = Task.Run(async () => await _clientService.GetAllClients()).Result;
foreach (var client in clients)
{
clientViewModel = new ClientViewModel();
clientViewModel.ClientId = client.ClientId;
clientViewModel.Active = client.Active;
clientViewModel.Address = client.Address;
clientViewModel.City = client.City;
clientViewModel.ClienteName = client.ClienteName;
clientViewModel.ComercialEmployeeId = client.ComercialEmployeeId;
clientViewModel.Confirmed = client.Confirmed;
clientViewModel.CountryId = client.CountryId;
clientViewModel.CreationDate = client.CreationDate;
clientViewModel.DANE = client.DANE;
clientViewModel.Department = client.Department;
clientViewModel.ElectronicBillingEmail = client.ElectronicBillingEmail;
clientViewModel.Eliminated = client.Eliminated;
clientViewModel.NIT = client.NIT;
clientViewModel.PostalCode = client.PostalCode;
clientViewModel.Phone = client.Phone;
listClientViewModels.Add(clientViewModel);
}
return listClientViewModels;
}
Here is how I add the token in postman.
fix your startup, add before app.UseEndpoints
....
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
.....
Add the options.Events to your AddJwtBearer:
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = c =>
{
// break point here
return Task.CompletedTask;
},
};
});
Then you'll get some context.exception:
(right-click image, open in new window/tab)
You should never do this on asp.net
// Task.Run is a no no
Task.Run(async () => await _clientService.GetAllClients()).Result;
You're using locking up 2 threads at the same time for no benefit.
Instead:
// GET api/clients
[HttpGet]
[Authorize]
public Task<IEnumerable<ClientViewModel>> Get()
and
var clients = await _clientService.GetAllClients();
For .Net Core you need to use a authorization provider like Identity Server:
https://identityserver4.readthedocs.io/en/latest/
I figure it out I had this code that overwrite the [Authorize] attribute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = (User)context.HttpContext.Items["User"];
if (user == null)
{
// not logged in
context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
}
}
}
I removed it and now it work I dont understand why the code was there. Sorry, for my question.
Authorize signalR Hub not getting connect to hub with front end angular, unable to invoke jwt access token to hub connection. How to connect Authurized signalr hub in asp core with angular project
i have the following code in my project,
Here my Code
public class Startup
{
readonly string CorsPolicy = "CorsPolicy";
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddCors(options =>
{
options.AddPolicy(name: CorsPolicy,
builder =>
{
builder
.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins("http://localhost:3000", "http://localhost:4200", "http://localhost:1234")
.AllowCredentials();
});
});
services.AddControllers();
services.AddSignalR()
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
//jwt token
services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])),
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = "Bearer " + context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/connecthub")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ApplicationDatabaseContext context)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors(CorsPolicy);
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<signalR_Hub>("/connecthub");
});
}
}
}
This my Hub connection
[Authorize]
public class sample_Hub :Hub
{
}
This my controller
[Authorize]
[HttpPut("{id}")]
public async Task<IActionResult> Putfilecreations(string id, filecreations filecreations)
{
var entity = _context.file_creations.Where(x => x.file_creation_id == id).FirstOrDefault();
entity.client_type_id = filecreations.client_type_id;
_context.file_creations.Update(entity);
await _context.SaveChangesAsync();
await _hubContext.Clients.All.SendAsync("FileUpdated", entity);
return Ok(entity);
}
This my Angular code for connect Hub
this.hubConnection = new HubConnectionBuilder()
.withUrl(environment.baseUrls.server + 'connecthub')
.configureLogging(LogLevel.Information)
.build();
Try to add CORS this way, the order is important:
services.AddCors(options =>
{
options.AddPolicy(CorsPolicy, builder => builder.WithOrigins("http://localhost:3000", "http://localhost:4200", "http://localhost:1234"")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.SetIsOriginAllowed((host) => true));
});
I am trying to add an authentication layer in my API with OpenId and OAuth2 but when I make the call, passing the token in the header, I keep receiving
Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolException: Message contains error: 'invalid_request', error_description: 'unauthorized_client', error_uri: 'error_uri is null'.
I have configured AWS Cognito, the startup.cs, and I can successfully get a JWT token from Swagger or Postman.
Do you see something configured badly, or missing, in my startup.cs?
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication(c =>
{
c.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
c.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
c.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(c =>
{
c.ResponseType = Configuration["Authentication:Cognito:ResponseType"];
c.MetadataAddress = Configuration["Authentication:Cognito:MetadataAddress"];
c.ClientId = Configuration["Authentication:Cognito:ClientId"];
c.Authority = "https://auth.myauthserver.com";
c.Scope.Add("myscope");
c.GetClaimsFromUserInfoEndpoint = true;
});
// Configure named auth policies that map directly to OAuth2.0 scopes
services.AddAuthorization(c =>
{
c.AddPolicy("myscope", p => p.RequireClaim("scope", "myscope"));
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "MyAPI", Version = "v1"
});
c.OperationFilter<AddAuthHeaderOperationFilter>();
c.AddSecurityDefinition("bearer", //Name the security scheme
new OpenApiSecurityScheme{
Flows = new OpenApiOAuthFlows()
{
ClientCredentials = new OpenApiOAuthFlow()
{
TokenUrl = new Uri("https://auth.myauthserver.com/oauth2/token"),
Scopes = new Dictionary<string, string>(){ {"myscope", "Access API"}},
AuthorizationUrl = new Uri("https://auth.myautherver.com/oauth2/authorize")
}
},
Type = SecuritySchemeType.OAuth2,
OpenIdConnectUrl = new Uri("https://cognito-idp-url.../.well-known/openid-configuration"),
BearerFormat = "JWT",
In = ParameterLocation.Header,
Scheme = "bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement{
{
new OpenApiSecurityScheme{
Reference = new OpenApiReference{
Id = "Bearer",
Type = ReferenceType.SecurityScheme
},
OpenIdConnectUrl = new Uri("https://cognito-idp-url.../.well-known/openid-configuration")
},new List<string>(){"myscope"}
}
});
});
services.AddOptions();
services.AddCors(options => options.AddPolicy("CorsPolicy",
builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseHsts();
app.UseCors(policy =>
policy
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowAnyOrigin()
.SetPreflightMaxAge(TimeSpan.FromDays(1))
);
app.UseCors("CorsPolicy");
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
app.UseAuthentication();
app.UseSwagger();
app.UseSwaggerUI(c => {
c.SwaggerEndpoint($"v1/swagger.json", "MyAPI v1");
c.OAuth2RedirectUrl("https://auth.myauthserver.com/signin-oidc");
});
}
The controller has the [Authorize] and [Produces("application/json")] attributes on it.
This is the AddAuthHeaderOperationFilter:
public class AddAuthHeaderOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var isAuthorized = (context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any()
|| context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any())
&& !context.MethodInfo.GetCustomAttributes(true).OfType<AllowAnonymousAttribute>().Any(); // this excludes methods with AllowAnonymous attribute
if (!isAuthorized) return;
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
var jwtbearerScheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "bearer" }
};
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement { [jwtbearerScheme] = new string []{} }
};
}
}
This is part of the appsettings.json:
"Authentication": {
"Cognito": {
"ClientId": "47...",
"IncludeErrorDetails": true,
"MetadataAddress": "https://cognito-idp-url.../.well-known/openid-configuration",
"RequireHttpsMetadata": false,
"ResponseType": "code",
"SaveToken": true,
"TokenValidationParameters": {
"ValidateIssuer": true
}
}
},
Cognito is configured to accept Client Credentials OAuth flow and the Allowed Auth Scope myscope selected.
And this is a curl example:
curl -X GET "https://localhost:5001/v1/MyController/2" -H "accept: application/json" -H "Authorization: Bearer eyJraWQiOiI2dGFPTW..."
Thank you
I have finally found a solution to my question. Not everything was configured well, I'll leave here the startup.cs that works with the Client Credentials flow and allows the authentication from Swagger and OpenAPI.
Authentication:Cognito:Authority is configured in the appsettings.json as "Authority": "https://cognito-idp.eu-west-1.amazonaws.com/your_region-id"
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
services.AddControllers();
Initializer.RegisterServices(services);
services.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
var json = new WebClient().DownloadString(
parameters.ValidIssuer + "/.well-known/jwks.json");
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
return (IEnumerable<SecurityKey>) keys;
},
ValidIssuer = Configuration["Authentication:Cognito:Authority"],
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
ValidateLifetime = true,
ValidateAudience = false,
};
options.IncludeErrorDetails = true;
options.SaveToken = true;
options.Authority = Configuration["Authentication:Cognito:Authority"];
options.RequireHttpsMetadata = true;
})
.AddOpenIdConnect(options =>
{
options.Authority = Configuration["Authentication:Cognito:Authority"];
options.RequireHttpsMetadata = false;
options.ClientId = Configuration["Authentication:Cognito:ClientId"];
options.Scope.Add("myscope");
});;
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(ApiVersion, new OpenApiInfo
{
Title = "MyAPI", Version = "v1"
});
c.OperationFilter<AddAuthHeaderOperationFilter>();
c.AddSecurityDefinition("bearer", //Name the security scheme
new OpenApiSecurityScheme
{
Flows = new OpenApiOAuthFlows
{
ClientCredentials = new OpenApiOAuthFlow
{
TokenUrl = new Uri("https://auth.myauthserver.com/oauth2/token"),
Scopes = new Dictionary<string, string> {{"myscope", "Access API"}},
AuthorizationUrl = new Uri("https://auth.myauthserver.com/oauth2/authorize")
}
},
Type = SecuritySchemeType.OAuth2,
OpenIdConnectUrl =
new Uri(
"https://cognito-idp.eu-west-1.amazonaws.com/your_region-id/.well-known/openid-configuration"),
BearerFormat = "JWT",
In = ParameterLocation.Header,
Scheme = "bearer"
});
});
services.AddOptions();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseHsts();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
app.UseAuthentication();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"{ApiVersion}/swagger.json", "MyAPI v1");
c.OAuth2RedirectUrl("https://auth.myauthserver.com/signin-oidc");
c.OAuthConfigObject = new OAuthConfigObject
{
ClientId = Configuration["Authentication:Cognito:ClientId"],
UsePkceWithAuthorizationCodeGrant = true
};
});
}
I hope it helps.