My team is building a web api in .net core. We use token authentication to authenticate any client, the token comes form the Azure AD.
We put this code in Startup.cs
app.UseJwtBearerAuthentication(options => {
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.Authority = "https://login.windows.net/1da3434a9-e5a6-4e9e-80c3-aca8ed37cb03";
options.Audience = "https://test.onmicrosoft.com/test_api";
options.RequireHttpsMetadata = false;
});
services.AddCors(options =>
{
// Define one or more CORS policies
options.AddPolicy("AllowAll",
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
This one only work when we allow anonymous access. I checked the token, and the token is valid. But every time we hit controller it will throw cors error. even though we enable cors
The sequence of adding middleware does matter!
To enable CORS for your entire application add the CORS middleware to
your request pipeline using the UseCors extension method. Note that
the CORS middleware must proceed any defined endpoints in your app
that you want to support cross-origin requests (ex. before any call to
UseMvc).
You should put services.AddCors above almost everything that might redirect your users via 401.
You cannot combine AllowCredentials & AllowAnyOrigin. Pick a specific origin and your request will likely work.
Related
Currently, we have a working OAuth authentication for our ASP.NET Core 5 Web API. We would like to add a certificate authentication as well to be double sure of our caller. Is there a way to have both of them? I tried the below code but it overrides one over the other.
services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
.AddAzureADBearer(options =>
{
options.Instance = aADInstance;
options.ClientId = clientIdWithScope;
options.Domain = aADDomain;
options.TenantId = aADTenantId;
}
)
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
Changing default policy
// Add authentication schemes before, we already did this, so I would skip this part
// Change default Authorization policy
services.AddAuthorization(cfg =>
cfg.DefaultPolicy =
new AuthorizationPolicyBuilder(CertificateAuthenticationDefaults.AuthenticationScheme,
AzureADDefaults.JwtBearerAuthenticationScheme).RequireAuthenticatedUser().Build());
[Authorize] attribute now would require all http request to satisfied both CertificateAuthenticationDefaults.AuthenticationScheme and AzureADDefaults.JwtBearerAuthenticationScheme, that might be not the behavior we want for all of our endpoint, so, be careful with this approach.
Add our own policy
// Add authentication schemes before, we already did this, so I would skip this part
// Adding our own policy
services.AddAuthorization(options =>
{
options.AddPolicy("ComposedPolicy", p =>
{
p.AuthenticationSchemes = new List<string>
{CertificateAuthenticationDefaults.AuthenticationScheme, AzureADDefaults.JwtBearerAuthenticationScheme};
p.RequireAuthenticatedUser();
p.Build();
});
});
[Authorize] attribute behavior now would be untouch, but whenever we want to use our custom policy, we must specify them by [Authorize(Policy = "ComposedPolicy")].
Just choose the approach that suit the best.
We are developing a web application communicating with its backend API. API is written in .NET Core and is running in Azure and is using OpenID authentication against Azure Active Directory. Configuration of the authentication process is below (as you can see we're using cookie based authentication):
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = AzureADDefaults.CookieScheme;
options.DefaultChallengeScheme = AzureADDefaults.AuthenticationScheme;
options.DefaultSignInScheme = AzureADDefaults.CookieScheme;
})
.AddAzureAD(options =>
{
configuration.Bind("AzureAd", options);
});
services.Configure<CookieAuthenticationOptions>(AzureADDefaults.CookieScheme, options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.MaxAge = new TimeSpan(7, 0, 0, 0);
});
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.ValidateIssuer = false;
});
We want to test our application API in Postman and we have set up a request with authentication against AAD (configuration below). Postman is able to make it through authentication and we get the access_token, however the request to API fails.
When we compared Postman cookies and browser cookies we discovered, that browser contains a cookie postman is missing .AspNetCore.AzureADCookie. It's Friday afternoon and we really got into desperation phase and have no clue what may be wrong. How can we make Postman to call AAD in a way it returns such cookie in response and adds it to the API request.
You should be able to use your browser cookies by installing Postman Interceptor extension.
Please try the same and let me know if it works.
https://learning.postman.com/docs/sending-requests/capturing-request-data/interceptor/#syncing-cookies
You can also ref the following ->
Postman is not using cookie
I am trying to figure out if its possible to write an ASP.NET Core API that consumes an identity server token using either Reference Tokens or JWT tokens based on whatever I've configured my identity server to use. The back-end configuration for IS4 is pretty easy, I'm just not convinced that I can configure 2 different token middlewares and my service will both be ok with it and know what to do.
So the idea is:
If my API gets a jwtToken, it attempts to use the jwt middleware for authorization back to identity server.
If my API gets a reference token, it attempts to use the introspection middleware for authorization back to identity server.
Obviously, if the wrong type of token is provided for whatever is configured on the IS4 service, it will fail.
Handling the token endpoint and revocation endpoint should also be easy enough, it's just the middleware magic I'm concerned with.
I know typically you wouldn't want to do this but we have a niche use case for it. All I'm currently concerned with is whether or not its even possible. I'm not familiar with how the auth middleware works in the back-end.
According to the Identity Server 4 Protecting APIs document, we can see that it supports to use both JWTs and reference tokens in asp.net core.
You can setup ASP.NET Core to dispatch to the right handler based on the incoming token, see this blog post for more information.
services.AddAuthentication("token")
// JWT tokens
.AddJwtBearer("token", options =>
{
options.Authority = Constants.Authority;
options.Audience = "resource1";
options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
// if token does not contain a dot, it is a reference token
options.ForwardDefaultSelector = Selector.ForwardReferenceToken("introspection");
})
// reference tokens
.AddOAuth2Introspection("introspection", options =>
{
options.Authority = Constants.Authority;
options.ClientId = "resource1";
options.ClientSecret = "secret";
});
Supporting both JWTs and reference tokens
In addition to #Zhi Lv post you might need to add Authorization policy, Authentication Schemes to allow validating JWT and reference tokens.
Here is the sample code template replace api name, api secret and audience appropriatly.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddJwtBearer(Options =>
{
Options.Authority = "https://identity.domain.com/identity/";
Options.Audience = "resource1"; //your api baseurl e.g if you want userinfo_endpoint specify https://identity.domain.com/identity/connect/userinfo
Options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://identity.domain.com/identity/";
options.ApiName = "api name / scope";
options.ApiSecret = "api secret / scope secret";
});
services.AddAuthorization(options =>
{
options.AddPolicy("tokens", x =>
{
x.AddAuthenticationSchemes("jwt", "introspection");
x.RequireAuthenticatedUser();
});
});
}
The way I would do it is to use introspection and claims caching in both cases, so that the API does not need to know or care which type of access token it receives.
The introspection would only occur when an access token is first received. Subsequent requests with the same token then use cached claims.
RESOURCES
Blog Post
Sample C# Code
I have application where its using Cookie and Bearer token. Since i dont want update each Authorize by providing a schemas I did rewrite default schemas:
services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(OAuthValidationDefaults.AuthenticationScheme,
CookieAuthenticationDefaults.AuthenticationScheme,
"Identity.Application")
.RequireAuthenticatedUser()
.Build();
});
services.AddAuthentication()
.AddExternalAuthProviders(Configuration)
.AddFlymarkOpenIdConnectServer()
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/Identity/Account/LogIn";
options.SlidingExpiration = true;
options.Events.OnRedirectToLogin = OnRedirectToLogin;
})
.AddOAuthValidation(OAuthValidationDefaults.AuthenticationScheme,
o=>o.Events.OnCreateTicket = OnCreateTicket);
services.ConfigureApplicationCookie(config =>
{
config.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = OnRedirectToLogin
};
});
I thought that if I add CookieAuthenticationDefaults.AuthenticationScheme then it will work but cookies are not working untill I add Identity.Application
So my question is why its not working or where is constant to use instead of hardcoded string
ASP.NET Core Identity uses its own instances of the cookie authentication handler, magically registered for you when you call services.AddIdentity().
As you figured out, these instances don't use the default scheme name but a name that starts with the Identity. prefix.
In your code, you're basically registering a new cookie handler instance with the default scheme name. But since nothing in your code uses it to issue cookies, it can't validate anything and will always return unauthenticated tickets, which is why it doesn't work until you add the magical Identity.Application, which corresponds to the main cookie handler instance used by ASP.NET Core Identity.
The constant you're looking for is in IdentityConstants.
Note: be extremely careful before adding a default policy that accepts both authentication cookies and bearer tokens, as your app might be vulnerable to CSRF if it doesn't use antiforgery (which is normally the case when developing an API).
I implemented a token server using Identity Server 4.
I added a custom API endpoint to the token server and struggle with the authentication. The custom endpoint is inherited from ControllerBase and has 3 methods (GET, POST, DELETE).
I intend to call the custom endpoint from within another API using a dedicated client with credentials (server to server) implemented as HttpClient in .NET Core. There is no user involved into this.
For getting the access token I use the IdentityModel DiscoveryClient and TokenEndpoint.
So in sum I did the following so far:
setup "regular" identity server and validate it works -> it works
implement custom endpoint and test it without authorizatio -> it works
add another api resource ("api.auth") with a custom scope "api.auth.endpoint1"
setup a client with client credentials allowing access to scope "api.auth.endpoint1".
implement the HttpClient and test setup -> I get an access token via the Identity Model Token Endpoint.
Now, when I call the endpoint using the HttpClient with the access token I received I get response code 200 (OK) but the content is the login page of the identity server.
The documentation of Identity Server 4 state the use of
services.AddAuthentication()
.AddIdentityServerAuthentication("token", isAuth =>
{
isAuth.Authority = "base_address_of_identityserver";
isAuth.ApiName = "name_of_api";
});
as well as the use of
[Authorize(AuthenticationSchemes = "token")]
Unfortunatly the compiler state that .AddIdentityServerAuthentication can't be found. Do I miss a special nuget?
The nugets I use on the token server so far are:
IdentityServer4 (v2.2.0)
IdentityServer4.AspNetIdentity (v2.1.0)
IdentityServer4.EntityFramework (v2.1.1)
Figured out that part. The missing nuget for AddIdentityServerAuthentication is:
IdentityServer4.AccessTokenValidation
Struggling with the authorization based on the custom scope.
Does anyone know how the security has to be configured?
Configure a client with ClientGrantTypes = client_credentials and your api like this:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth";
});
Where ApiName is the name of the resource. Please note that resource != scope. In most samples the resource name is equal to the scope name. But not in your case, where resource name is api.auth and scope name is api.auth.endpoint1.
Configure the client to request the scope.
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, secret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api.auth.endpoint1");
IdentityServer will lookup the Resource name and add that to the token as audience (aud) while the scope is added as claim with type scope.
This should be enough to make it work. Also check the sample project.
Custom authentication scheme and scope based policies for different access rights bundled together looks like that:
// Startup.ConfigureServices
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication("CustomAuthEndpointsAuthenticationScheme", options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth"; //IdentityServer4.Models.ApiResource.Name aka Audience
});
services.AddAuthorization(options =>
{
options.AddPolicy("Endpoint1Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint1"); } ); //IdentityServer4.Models.Scope.Name
options.AddPolicy("Endpoint2Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint2"); } ); //IdentityServer4.Models.Scope.Name
} );
// securing the custom endpoint controllers with different access rights
[Authorize(AuthenticationSchemes = "CustomAuthEndpointsAuthenticationScheme", Policy = "Endpoint1Policy")]
It seems not to interfere with the IdentityServer4 default endpoints nor with the ASP.NET Core Identity part.