I am not sure I completely understood the changes for Microsoft.Identity.Web but I was following an article (given by Microsoft here) Where it described how to change in startup
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
to
services.AddMicrosoftIdentityWebAppAuthentication(Configuration);
while this looks good and easy I have a little more work because I have the following snippet in my existing code
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => this.configuration.Bind("AzureAd", options))
.AddJwtBearer(options =>
{
//this code used to validate signing keys
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None).GetAwaiter().GetResult();
var tenantId = this.configuration["TenantId"];
var validIssuer = $"https://sts.windows.net/{tenantId}/";
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = validIssuer,
ValidAudience = this.configuration["ClientId"],
IssuerSigningKeys = openIdConfig.SigningKeys,
};
});
To give you a little bit of context we have two variations with this application
User Login and do some staff (here user will get Microsoft login dialog to login using his/her credential)
Microsoft Azure calls our endpoint with some token and we need to validate that token.
The JWTvaliation section you see above is for the 2nd item where once we received a token we validate that token without login and UI workflow.
Question:
The above code is working correctly. The only issue here is if we like to use Microsoft.Identity how should we use the second item (JWT) because services.AddAuthentication().AddAzureAD returns IAuthenticationBuilder which we use further to add AddJwtBearer, While services.AddMicrosoftIdentityWebAppAuthentication does not return IAuthenticationBuilder.
AddMicrosoftIdentityWebAppAuthentication is actually just a fancy way to do the following:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(…)
So it configures the default scheme to be the OIDC scheme and runs AddMicrosoftIdentityWebApp to configure whatever this ends up doing.
Now, AddAuthentication can actually be called multiple times on the service collection. You just need to be careful not to reconfigure things incorrectly. The parameterless function does not do that, so it is a good way to access the IAuthenticationBuilder to further configure authentication.
That means that you can change your code like this:
// configure Microsoft Identity Web first
// this also sets the default authentication to OIDC
services.AddMicrosoftIdentityWebAppAuthentication(Configuration);
// retrieve an authentication builder without changing the default
services.AddAuthentication()
// add JWT bearer now
.AddJwtBearer(options =>
{
// …
});
Related
This is my first time trying to create a Web API in Visual Studio using C#.I'm using a database which has a table User. When I want to make request such as logging in and seeing more information I want to authenticate the user. The login method seems to be working just fine, it checks the user's credentials, validates them in the database, lets the user login if the credentials are right and generates the jwt bearer token. I'm generating the token based on claims. However, I don't know how to limit any of the GET, POST, PUT methods for authorized users based on the token. I'm confused as to how will the program know whether you are logged in and can make other requests or not? Should I create another field in the User model which will store let's say true or false for authorization(the token)? And how do I test it using swagger or postman?
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt: Issuer"],
ValidAudience = Configuration["Hwt: Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
Hopefully this can get you pointed in the right direction, you need to wire up a few things for this. You will need to tweak this answer to whatever token scheme you are using, but hopefully this gets you started and you can google from here.
In the startup.cs file add something like this
services.AddAuthorization(options =>
{
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.Name);
});
});
In startup.cs where you are setting your IApplicationBuilder app:
app.UseAuthentication();
app.UseAuthorization();
Then in each controller you can decorate the top of the controller with these attributes.
[ApiController]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Authorize(AuthenticationSchemes = "BasicAuthentication")]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[Route("[controller]")]
public class CustomerController : ControllerBase
You don't need all 3 of these, tailor this to whatever token scheme you are using.
You can also decorate the individual endpoints with these attributes, when doing it at the top of the controller class it applies them to all the endpoints.
I'm trying to use Microsoft Identity (formerly: Azure AD) authentication in an ASP.NET web application running on .NET 6
I've used this code to configure authentication in my startup class ConfigureServices method:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options => {
options.Instance = appSettings.UserSettings.AzIdInstance;
options.Domain = appSettings.UserSettings.AzIdDomain;
options.TenantId = appSettings.UserSettings.AzIdTenantId;
options.ClientId = appSettings.UserSettings.AzIdClientId;
options.CallbackPath = appSettings.UserSettings.AzIdCallbackPath;
options.SignedOutCallbackPath = appSettings.UserSettings.AzIdSignOutCallbackPath;
});
services.AddAuthorization();
Then in the Configure method, I've added:
app.UseAuthentication();
app.UseAuthorization();
When I try to access a controller action protected by the [Authorize] attribute, it correctly redirects me to the microsoft login page, however after I log in when the app then tries to redirect to my callback path (/signin-oidc) the connection gets reset and I get this browser error:
What am I doing wrong here? Is there a good example online on how to properly configure this?
Apparently, this error was happening because I had set the cookie's SameSite attribute to None with this code (I need it to be None for some cross-domain calls that are done to the server):
services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
//In-memory cookie store, so sessions are forcibly killed when the webapp restarts
//This ensures that the user's Claims are up to date - remember to restart the service when Azure AD security group memberships are changed!
options.SessionStore = services.BuildServiceProvider().GetService<MemoryCacheTicketStore>();
//We want to disable the same-site policy, otherwise cross-domain API calls from JS won't work
options.Cookie.SameSite = SameSiteMode.None;
});
apparently this by itself causes the error. To fix it, I had to also add the following code:
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.HandleSameSiteCookieCompatibility(s => false);
options.MinimumSameSitePolicy = SameSiteMode.None;
});
this seems to have fixed the problem.
As described in https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-3.1#session-state , one can add session to one's web app like so in Start.ConfigureServices
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
and in Startup.Configure
App.UseSession()
There is also the possibility to use cookie authentication without identity via the authentication middleware, as described here https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
...
});
My question is, if I, in Startup.Configure, use both
App.UseSession()
App.UseAuthentication()
Which Cookie settings will be used? Will the Cookie settings in services.AddSession be completely irrelevant (because the Authentication middleware also uses Session Cookies to keep track of users, right? Or am I completely wrong about that)? Or will they just be two different sessions/services running at the same time?
I am aware that Startup.Configure (the HTTP pipeline) is order-sensitive, as described my Microsoft "Adding the middleware to the app processing pipeline is order sensitive—it only affects downstream components registered in the pipeline." My second question is thus, if I put App.UseCookiePolicy(options) before the above, would it override the settings?
App.UseCookiePolicy()
Thanks in advance for any answers!
I have a web based app. This app allows users to sign up/in using Google Auth as per this code in Startup.cs
services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = Configuration["ClientId"];
googleOptions.ClientSecret = Configuration["CliSecret"];
...
});
This all works nicely with the out-of-the-box Identity system so I can register users.
However, I also want users to be able to 'connect' to other Google services with separate accounts after the sign up in a separate area of the site.
For example, I might want a user to connect their AdWords account.
They will authenticate with Google via a non-Identity flow and the relevant info (token, refresh token etc) will be stored independantly in the db (i.e it won't store a 'User' in the AspNetUSers table).
Can I change the authentication scope in the controller before I make my initial call to google?
It'd be nice to utilize the same Authentication service but with some extra scope in this case. Is that possible?
Alternatively, have another Google section in Startup.cs...maybe like:
services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = Configuration["ClientId"];
googleOptions.ClientSecret = Configuration["CliSecret"];
googleOptions.Scope.Add("https://www.googleapis.com/auth/adwords"); //*** THIS IS THE EXTRA SCOPE NEEDED ***
...
});
We had similar problem, our Identity Provider should be able to login users of defferent clients with different Google account
We decided to add multiple Google areas as you suggested. The main point here is that each area (which defines some google account) uses unique cookie scheme.
When we create login URL, we get google account needed for that client, get it's cookie scheme and create correct URL for Google Authenticate button
code example:
public static class AuthenticationBuilderGoogleAdder
{
public static AuthenticationBuilder AddGoogleAuth(this AuthenticationBuilder authenticationBuilder, IServiceCollection services)
{
var serviceProvider = services.BuildServiceProvider();
// create IThirdPartyProvidersProvider realization with GetByProviderCode method
var authThirdPartyProvidersProvider = serviceProvider.GetService<IThirdPartyProvidersProvider>();
var googleProviders = authThirdPartyProvidersProvider.GetByProviderCode("google");
googleProviders.ForEach(p =>
{
authenticationBuilder = authenticationBuilder.AddGoogle(p.CookieScheme, options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = p.ClientId;
options.ClientSecret = p.ClientSecret;
});
});
return authenticationBuilder;
}
}
register it as
services.AddAuthentication()
.AddGoogleAuth(services)
We call services.BuildServiceProvider() in order to create another container with services which were already registered in DI, in order to get Google accounts with different cookie schemas from the DB
I'm using cookie auth with the following settings:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,
AutomaticAuthenticate = true,
AutomaticChallenge = false,
ExpireTimeSpan = new TimeSpan(2, 0, 0)
});
I've assumed that using the [Authorize] attribute with a policy declared requires a user to be authenticated before evaluating custom requirements, however, it's not the case.
This has also been attempted (note the call to RequireAuthenticatedUser()):
options.AddPolicy("MyPolicy",
policy =>
{
policy.RequireAuthenticatedUser();
policy.Requirements.Add(
new SomeRequirement(serviceProvider.GetService<IMyService>()));
});
However, even then, in my AuthorizationHandler for SomeRequirement, HandleRequirementAsync() still gets executed. Why?
I really, really, really don't want to check if user is authenticated in every AuthenticationHandler like this:
if (!context.User.Identity.IsAuthenticated)
{
context.Fail();
return Task.CompletedTask;
}
That's by design. When you specify a custom authorization policy, you replace the default one. The workaround is simple:
[Authorize(Policy="foo")]
[Authorize]
This combines your policy AND default policy.
Explanation: this is how policies are combined: AuthorizationPolicy. You could see policyProvider.GetDefaultPolicyAsync() is called only if authorizeDatum.Policy is empty (i.e. if custom policy is not specified).
FYI: default policy has only one requirement: DenyAnonymousAuthorizationRequirement.
Its essential part is:
if (!userIsAnonymous)
{
context.Succeed(requirement);
}
return TaskCache.CompletedTask;
So it doesn't fail on an anonymous user, it passes control-flow to the next requirement. Chain of requirements will continue until explicit context.Fail() call or chain's end. To fail-fast on an anonymous user, write the custom requirement:
if (userIsAnonymous) context.Fail()
And place it first in the list (order of handlers here determines their execution order in request pipeline):
services.AddScoped<IAuthorizationHandler, CustomFailAuthorizationRequirementHandler>();
//Register other authorization handlers
Default DenyAnonymousAuthorizationRequirement can be overriden with this custom requirement (requirement handler will be called on every authorization (custom or default)):
services.AddAuthorization(options => {
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddRequirements(new CustomFailAuthorizationRequirement)
.Build();
//Add other policies
});
Don't want to deal with unauthenticated requests in your custom policies? Do what's listed in blowdart's ASP.NET Authorization Workshop on GitHub: authorize all endpoints with a default policy.
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
Then use [AllowAnonymous] where needed.