I wanna implement JWT authentication in the Ocelot API gateway, I followed ocelot documentation carefully and also implemented that. But I got an error that not any idea for solving that.
I used this section of the documentation for enabling authentication.
My received error:
System.AggregateException: 'One or more errors occurred. (Unable to
start Ocelot, errors are: Authentication Options
AuthenticationProviderKey:BaseAuthenticationSchema,AllowedScopes:[] is
unsupported authentication provider)'
Used packages:
Ocelot(17.0.0)
Microsoft.AspNetCore.Authentication.JwtBearer(5.0.11)
Also sections of my codes for more specification:
Program.cs:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddJsonFile($"ocelot.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
})
.ConfigureServices(s =>
{
s.AddOcelot();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseSerilog((_, config) =>
{
config
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.File(#"Logs\AllHttpRequestsLog.txt", rollingInterval: RollingInterval.Day);
})
.Configure(app =>
{
app.UseMiddleware<HttpRequestsLoggingMiddleware>();
app.UseOcelot().Wait();
});
});
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Adding Authentication
var baseAuthenticationProviderKey = "BaseAuthenticationSchema";
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
// Adding Jwt Bearer
.AddJwtBearer(baseAuthenticationProviderKey, options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidAudience = "ValidAudience",
ValidIssuer = "ValidIssuer ",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("IssuerSigningKey"))
};
});
services.AddControllers();
services.AddOcelot(_configuration);
}
And finally used configuration for the ocelot:
{
"DownstreamPathTemplate": "/api/v1/banks",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 44371
}
],
"UpstreamPathTemplate": "/api/market/banks",
"UpstreamHttpMethod": [ "Get" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "BaseAuthenticationSchema",
"AllowedScopes": []
}
}
I investigated all articles and also the ocelot GitHub page like this open issue, But my problem was not solved. Can anyone help me?
Thanks a lot.
Finally, I solved my problem using this comment on the Ocelot GitHub page open issues.
Just moved the authentication configuration from the startup.cs file to the program.cs file on the .ConfigureServices section.
Like this:
.ConfigureServices(s =>
{
// Adding Authentication
var baseAuthenticationProviderKey = "BaseAuthenticationSchema";
s.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
// Adding Jwt Bearer
.AddJwtBearer(baseAuthenticationProviderKey, options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidAudience = "ValidAudience",
ValidIssuer = "ValidIssuer",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Secret"))
};
});
s.AddOcelot();
})
Also, removed that configuration from the startup.cs class.
Related
Trying to restrict access to an endpoint only for users with the role "Manager".
when I open swagger, I use Login endpoint to login(it's successful) and then I try to run 'AddCategory' endpoint and response is 404.
[Authorize(Roles = "Manager")]
[HttpPost("addCategory")]
[SwaggerResponse((int)HttpStatusCode.OK, type: null)]
[SwaggerResponse((int)HttpStatusCode.Unauthorized)]
[SwaggerResponse((int)HttpStatusCode.Forbidden)]
[SwaggerResponse((int)HttpStatusCode.InternalServerError)]
public Category AddCategory([FromBody] Category category)
{
context.Categories.Add(category);
context.SaveChanges();
return category;
}
Same happens if I do not log in. If I remove Authorize attribute, everything works fine.
I checked UserRoles table and user I'm logging in with is Manager. Also tried to put only [Authorize] attribute instead of role specific and still didn't work. In Swagger, Login endpoint returns 200 response but is there any other way to check if I'm logged in or not?
EDIT
This is how I Generate JwtToken on Login.
var roleId = _appDbContext.UserRoles.Where(x => x.UserId == user.Id)
.Select(x=>x.RoleId)
.FirstOrDefault();
var role = _appDbContext.Roles.Where(x=>x.Id==roleId).FirstOrDefault();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim("Id", user.Id),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, role.Name)
}),
Expires = DateTime.UtcNow.AddSeconds(30), // 5-10
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
Decoded token looks like this
"alg": "HS256",
"typ": "JWT"
}.{
"Id": "3f4b1e8f-d15c-426e-9aa6-375ac53f27ae",
"email": "manager#example.com",
"sub": "manager#example.com",
"jti": "6e1fb89e-5408-4c61-8258-8d2c7cead4ab",
"role": "Manager",
"nbf": 1658758635,
"exp": 1658758665,
"iat": 1658758635
}.[Signature]
However, I've never added anything in [AspNetRoleClaims] table. Should I add data to that table too?
EDIT :
This is Program.cs
var key = Encoding.ASCII.GetBytes("llvudfvkwvepwkdnsnwmuulyvtrawppf");
var tokenValidationParams = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
RequireExpirationTime = false,
ClockSkew = TimeSpan.Zero
};
builder.Services.AddSingleton(tokenValidationParams);
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwt =>
{
jwt.SaveToken = true;
jwt.TokenValidationParameters = tokenValidationParams;
});
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Add app.UseAuthentication(); before app.UseAuthorization(); in program.cs file
I have a blazor app with azure ad auth, where human users are redirected to the microsoft log in screen etc, I now have a requirement that a tool needs to be able to access specific pages using a key in the header.
Is this possible? If so how do I add the second type of authorization where it effectively ignores the first type if the key is present?
I use the following code :
Connection Database
get conncentionString from appsettings.json :
var connectionString = builder.Configuration.GetConnectionString("AppContext") ?? throw new InvalidOperationException("Connection string 'AppContext' not found.");
Add DbContext :
builder.Services.AddDbContext<AppContext>(options =>
{
options.UseLazyLoadingProxies().UseSqlServer(connectionString, x => x.UseNetTopologySuite());
});
Add Identity
builder.Services.AddIdentity<IdentityUser, IdentityRole>(option => {
option.Password.RequireDigit = true;
option.Password.RequiredUniqueChars = 5;
option.Password.RequireLowercase = false;
option.Password.RequireNonAlphanumeric = false;
option.Password.RequireUppercase = false;
option.Password.RequiredLength = 16;
}).AddEntityFrameworkStores<AppContext>();
Add Authentication API JWT
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://Example.com",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("keyhash"))
};
});
Add Authentication Web Cookie
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options => {
options.LoginPath = "/Login";
options.LogoutPath = "/Logout";
options.AccessDeniedPath = "/AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromMinutes(100000);
});
Used packages
1. Microsoft.EntityFrameworkCore.Design Version="6.0.5"
2. Microsoft.EntityFrameworkCore.Proxies Version="6.0.5"
3. Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite Version="6.0.5"
I have an ASP.NET Core 2.2 application with the following configuration:
And this is my Startup.cs class
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = $"{ Configuration.GetValue<string>("AzureAdB2C:Instance") }/{ Configuration.GetValue<string>("AzureAdB2C:Tenant") }/{ Configuration.GetValue<string>("AzureAdB2C:SignUpSignInPolicyId") }/v2.0";
options.ClientId = Configuration.GetValue<string>("AzureAdB2C:ClientId");
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.ClientSecret = Configuration.GetValue<string>("AzureAdB2C:ClientSecret");
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
When I run this the B2C login page pops-up, however once I enter the credentials I get the error below. URL is redirected to https://localhost:xxxx/signin-oidc
How can I solve this problem?
This is the similar issue, the same error occurred when external login is canceled and redirects to the source application. You could use OnRemoteFailure to handle the exception.
AddOpenIdConnect("oidc", options => {
// ...
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = ctx =>
{
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
ctx.HandleResponse();
return Task.FromResult(0);
}
};
});
I am trying to configure my Jwt Bearer issuer key but, in production usually, I use Azure Key Vault wrapped by a KeyManager.
The KeyManager class is configured in Dependency Injection but, in ConfigureServices method I cannot use that (obviously), but if I cannot use that I cannot retrieve my key.
My solution at the moment is to build a temporary service provider and use it, but I think is not the state of the art (and I need to create two copies of singletons, not the best).
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
ServiceProvider sp = services.BuildServiceProvider();
IKeyManager keyManager = sp.GetService<KeyManager>();
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = keyManager.GetSecurityKeyFromName("jwt").Result,
ValidIssuer = "https://api.example.com",
ValidateIssuer = true
};
options.Audience = "https://api.example.com";
options.Authority = "https://api.example.com";
options.SaveToken = true;
});
Use Options pattern and implement IConfigureNamedOptions<JwtBearerOptions>:
public class ConfigureJwtBearerOptions : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly IKeyManager _keyManager;
public ConfigureJwtBearerOptions(IKeyManager keyManager)
{
_keyManager = keyManager;
}
public void Configure(JwtBearerOptions options)
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = _keyManager.GetSecurityKeyFromName("jwt").Result,
ValidIssuer = "https://api.example.com",
ValidateIssuer = true
};
options.Audience = "https://api.example.com";
options.Authority = "https://api.example.com";
options.SaveToken = true;
}
public void Configure(string name, JwtBearerOptions options)
{
Configure(options);
}
}
In Startup.cs:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer();
services.ConfigureOptions<ConfigureJwtBearerOptions>();
So, after more researches, I found this page on Microsoft's docs: Use DI services to configure options (refer also to that answer that refers to handling dynamically multiple Jwt Issuers).
services.AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
.Configure<IKeyManager>((options, keyManager) => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = keyManager.GetSecurityKeyFromName("jwt").Result,
ValidIssuer = "https://api.example.com",
ValidateIssuer = true
};
options.Audience = "https://api.example.com";
options.Authority = "https://api.example.com";
options.SaveToken = true;
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer();
There is a sample code for Jwt-Rsa-Hmac authentication on this web site with this repo.
I have been trying to convert it from Asp.Net Core 1 to Asp.Net Core 2.
I created a new Asp.Net Cor 2.1 project and after searching the changes it needed, I came up with this code.
It does create the token, but when using the token I always get 401 (unauthorized).
It's been a few days and no success...
I will be grateful if anyone could help me with this.
This is my startup class:
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();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<JwtSettings>(Configuration.GetSection("jwt"));
var x = services.AddSingleton<IJwtHandler, JwtHandler>();
var sp = services.BuildServiceProvider();
var jwtHandler = sp.GetService<IJwtHandler>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = jwtHandler.Parameters;
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseDeveloperExceptionPage();
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseAuthentication();
app.UseMvc();
}
And the rest of the sample code is in this repo.
Links I have already studies:
Token Authentication stopped working after migration from ASP.NET Core 1 to ASP.NET Core 2
The problem in your sample repository is, that you are creating new JwtBearerOptions here.
I changed it to this and it works just fine
services.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.IncludeErrorDetails = true;
o.RequireHttpsMetadata = false;
o.TokenValidationParameters = jwtHandler.Parameters;
o.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = c =>
{
c.NoResult();
c.Response.StatusCode = 401;
c.Response.ContentType = "text/plain";
return c.Response.WriteAsync(c.Exception.ToString());
}
};
});
I have sent you a pull-request.
I used this setting in .netcore2.1 and work for me:
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(cfg =>
{
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["BearerTokens:Issuer"],
ValidAudience = Configuration["BearerTokens:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["BearerTokens:Key"])),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
};
});