We're working on an API that allows users authenticating through a number of different providers. The individual providers are not an issue, but using them together is proving to be a challenge.
It seems that adding more than 1 provider throws a InvalidOperationException with "Scheme already exists: Bearer" when the application starts up.
Below is the ConfigureServices function from Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "Value";
options.Audience = "Value";
})
.AddMicrosoftIdentityWebApi(options =>
{
Configuration.Bind("AzureAdB2C", options);
options.TokenValidationParameters.NameClaimType = "name";
},
options => { Configuration.Bind("AzureAdB2C", options); });
services.AddControllers();
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});
}
I'm using the Microsoft example for authenticating with Azure AD as a starting point. Removing either the AddJwtBearer or AddMicrosoftIdentityWebApi calls works fine, but I need to configure both providers for our use-case.
Is there a way to do this with .NET Core 3.1 or up?
We can't register 2 authentications under same scheme name. So we need to register the 2 authentication schemes with different name(or one with default and another with a scheme name)
In my case I am registering 2 authentication schemes:
My own JWT scheme with our app name "MyAppName",
Azure AD authentication with JWT default scheme JwtBearerDefaults.AuthenticationScheme, as I was not able to add it with custom scheme name.
I was able to make it work with the following configuration:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer("MyAppName",options =>
{
options.Authority = "Value";
options.Audience = "Value";
})
.AddMicrosoftIdentityWebApi(Configuration, "AzureAd");
and Authorization configuration:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(
"MyAppName",
JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});
Related
I'm building an ASP.NET Core 3.1 Web API. For Authentication I'm using right now IdentyServer4. Now I got the additional requirement to apply Mutual TLS. When applying this this results in the following code in my Startup.cs (using: IdentityServer4.AccessTokenValidation (3.0.1) and Microsoft.AspNetCore.Authentication.Certificate (3.1.3)):
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "<baseaddress>";
options.ApiName = "<API>";
});
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
...
}
Now I'm facing the issue that my ClaimsPrincipal is overwritten by the Microsoft.AspNetCore.Authentication.Certificate. This is not desired because we use the claims from IdentityServer4 for allowing/denying functionality.
What's recommended in this situation?
These two lines are the problem in your code
services.AddAuthentication("Bearer")
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
You are overriding the default authentication scheme to certificate.
You should just add your certificate authentication
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "<baseaddress>";
options.ApiName = "<API>";
})
.AddCertificate(options =>
{
...
}
And use this code to authenticate with certificate
httpContext.AuthenticateAsync(CertificateAuthenticationDefaults.AuthenticationScheme);
I need the Authorize attribute in our Controller can accept two different tokens.
One token, is provided from one private ADFS, and other token is provided from AzureAd.
Several Ionic clients go to over ADFS, other Ionic clients go to over Azure AD
My dev scenario: ASP.NET Core 2.2 Web API
My actual startup.cs (abbreviated)
ConfigureService()
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer((options =>
{
options.Audience = Configuration["Adfs:Audience"];
options.Authority = Configuration["Adfs:Issuer"];
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
};
}));
}
I need here the other Authentication with AzureAD. How?
The Configure method of Startup.cs
Configure(…)
{
app.UseAuthentication()
}
With this code, only can access the ADFS Token and this users, can obtains result from the controllers. However, the AzureAD user's can't obtain access
I don't know how make this code for double token authorization, and our controllers can response if one token is from ADFS or other token is from AzureAD
You can set multiple JWT Bearer Authentication with different schema name :
services.AddAuthentication()
.AddJwtBearer("ADFS",options =>
{
options.Audience = Configuration["Adfs:Audience"];
options.Authority = Configuration["Adfs:Issuer"];
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
};
})
.AddJwtBearer("AAD", options =>
{
//AAD jwt validation configuration
});
If you want to make your controller/action to accept two jwt tokens , tokens from AAD or ADFS are ok to access your controller/action , you can make a policy to let both the AAD and ADFS authentication schemes tried to authenticate the request :
services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AAD", "ADFS")
.Build();
});
In addition , if you want to know which schema the token is from , you can check the particular claim in user's identity , or directly add authentication schema value to user claims in events :
options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents
{
OnTokenValidated = (context) =>
{
var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
//add your custom claims here
claimsIdentity.AddClaim(new Claim("schema", "AAD"));
return Task.FromResult(0);
}
};
And get in action after authentication :
var result = User.Claims.Where(c=>c.Type=="schema").FirstOrDefault().Value;
I have attempted to search for a solution to this problem, but have not found the right search text.
My question is, how can I configure my IdentityServer so that it will also accept/authorize Api Requests with BearerTokens?
I have an IdentityServer4 configured and running.
I also have configured a Test API on my IdentityServer like below:
[Authorize]
[HttpGet]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
In my startup.cs ConfigureServices() is as follows:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
// configure identity server with stores, keys, clients and scopes
services.AddIdentityServer()
.AddCertificateFromStore(Configuration.GetSection("AuthorizationSettings"), loggerFactory.CreateLogger("Startup.ConfigureServices.AddCertificateFromStore"))
// this adds the config data from DB (clients, resources)
.AddConfigurationStore(options =>
{
options.DefaultSchema = "auth";
options.ConfigureDbContext = builder =>
{
builder.UseSqlServer(databaseSettings.MsSqlConnString,
sql => sql.MigrationsAssembly(migrationsAssembly));
};
})
// this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.DefaultSchema = "auth";
options.ConfigureDbContext = builder =>
builder.UseSqlServer(databaseSettings.MsSqlConnString,
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 30;
})
// this uses Asp Net Identity for user stores
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<AppProfileService>()
;
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = authSettings.AuthorityUrl;
options.RequireHttpsMetadata = authSettings.RequireHttpsMetadata;
options.ApiName = authSettings.ResourceName;
})
and Configure() is as follows:
// NOTE: 'UseAuthentication' is not needed, since 'UseIdentityServer' adds the authentication middleware
// app.UseAuthentication();
app.UseIdentityServer();
I have a client configured to allow Implicit grant types and have included the configured ApiName as one of the AllowedScopes:
new Client
{
ClientId = "47DBAA4D-FADD-4FAD-AC76-B2267ECB7850",
ClientName = "MyTest.Web",
AllowedGrantTypes = GrantTypes.Implicit,
RequireConsent = false,
RedirectUris = { "http://localhost:6200/assets/oidc-login-redirect.html", "http://localhost:6200/assets/silent-redirect.html" },
PostLogoutRedirectUris = { "http://localhost:6200/?postLogout=true" },
AllowedCorsOrigins = { "http://localhost:6200" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"dev.api",
"dev.auth" // <- ApiName for IdentityServer authorization
},
AllowAccessTokensViaBrowser = true,
AllowOfflineAccess = true,
AccessTokenLifetime = 18000,
},
When I use Postman to access the protected API but it always redirects to the Login page even though a valid Bearer Token has been added to the Request header.
Commenting out the [Authorize] attribute will correctly return a response, but of course the User.Claims are empty.
When logging into the IdentityServer (via a browser) and then accessing the API (via the browser) it will also return a response. This time, the User.Claims are available.
There is an example co-hosting a protected API inside IdentityServer: IdentityServerAndApi
I quick comparison between their startup and yours is that they are calling AddJwtBearer instead of AddIdentityServerAuthentication:
services.AddAuthentication()
.AddJwtBearer(jwt => {
jwt.Authority = "http://localhost:5000";
jwt.RequireHttpsMetadata = false;
jwt.Audience = "api1";
});
TheAuthorize attribute also sets the authentication scheme:
[Authorize(AuthenticationSchemes = "Bearer")]
If you want to set a default authentication scheme to be one level above the policies (it is most relevant when you have multiple policies or no policies at all):
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.Authority = "http://localhost:5000";
o.RequireHttpsMetadata = false;
o.Audience = "api1";
});
Then you can simple use the [Authorize] tag attribute above the controller's method without polluting each authorization attribute with the sceme:
[Authorize]
public IActionResult GetFoo()
{
}
Found a better solution, configure in the Startup.cs:
services.AddAuthentication()
.AddLocalApi();
services.AddAuthorization(options =>
{
options.AddPolicy(IdentityServerConstants.LocalApi.PolicyName, policy =>
{
policy.AddAuthenticationSchemes(IdentityServerConstants.LocalApi.AuthenticationScheme);
policy.RequireAuthenticatedUser();
});
});
And use in controllers:
[Authorize(IdentityServerConstants.LocalApi.PolicyName)]
public class UserInfoController : Controller
{
...
}
Or even simpler:
services.AddLocalApiAuthentication();
Again, you still need
[Authorize(IdentityServerConstants.LocalApi.PolicyName)]
on your controller/method. And don't forget to add
IdentityServerConstants.LocalApi.ScopeName
to the allowed scopes/requested ones in the token.
See docs for more details.
Have 2 Web API's created using .Net Core 2.0 and hosted internally in IIS under windows authentication (anonymous disabled) on same server. Both API's run on same service account as well with appropriate permissions/rolegroups in Active Directory. However, get 401 unauthorized error when consuming one API from the other. Using HTTPClient to make API calls. Note that, it works when accessing the 2nd API endpoint directly via browser but not from another API.
Decorated with Authorize filter in controller
[Authorize(Policy = "ValidRoleGroup")]
Start up code in ConfigureServices in both api services as below.
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddAuthorization(options =>
{
options.AddPolicy("ValidRoleGroup", policy => policy.RequireRole(Configuration["SecuritySettings:ValidRoleGroup"]));
});
services.AddMvc(configure =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
configure.Filters.Add(new AuthorizeFilter(policy));
});
services.Configure<IISOptions>(options =>
{
options.AutomaticAuthentication = true;
options.ForwardClientCertificate = true;
});
services.AddMvc();
services.AddScoped<HttpClient>(c => new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true,
PreAuthenticate = true,
ClientCertificateOptions = ClientCertificateOption.Automatic,
}));
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
The 401 errors went away after adding registry entries as described in below article (Method 1)
https://support.microsoft.com/en-us/help/896861/you-receive-error-401-1-when-you-browse-a-web-site-that-uses-integrate
Note that the Value data should be your actual domain URL (XXX.com) and not machine name.
I am trying to use cookie based authentication in ASP.Net Core 2.0 Web API and trying to activate that using the following code. The signin page is hosted inan separate domain than the one the app is hosted. And I have added [Authorize] attribute to the controller.
At startup I can see the service code invoked in debugger.
My expectation is that when my web client use the web api service, the middleware will detect that header does not have the cookie and will redirect the client to the login page. Yet I am able to invoke the controller freely.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options => options.AddPolicy("AllowAll",
builder => builder.SetIsOriginAllowed(s => true)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()));
services.TryAddTransient<CorsAuthorizationFilter, CorsAuthorizationFilter>();
services.AddSwaggerGen(c =>
{
c.OperationFilter<FileOperationFilter>();
c.SwaggerDoc("v1", new Info
{
Title = "Collateral Management API",
Version = "v1"
});
});
services.AddMvcCore(options =>
{
options.Filters.Add(new CorsAuthorizationFilterFactory("AllowAll"));
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.AddApiExplorer()
.AddJsonFormatters(s => s.NullValueHandling = NullValueHandling.Ignore);
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(auth =>
{
auth.Cookie.Domain = "xxx.com";
auth.Cookie.Name = "xxx";
auth.LoginPath = "/signin";
auth.AccessDeniedPath = "/signin";
});
services.AddAuthorization(auth =>
{
auth.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
});
//...
}
and later ...
app.UseAuthentication()
Try adding:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
});
After services.AddMvc()
EDIT
Given the way you are adding MVC can you try:
// requires: using Microsoft.AspNetCore.Authorization;
// using Microsoft.AspNetCore.Mvc.Authorization;
services.AddMvcCore(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
AddMvcCore doesn't add the authorization services by default. You will also need to do AddMvcCore(...).AddAuthorization()