I have following code for Azure AD authentication:
services
.AddAuthorization(options =>
{
options.AddPolicy(name, builder =>
{
builder
.AddAuthenticationSchemes(AzureADDefaults.AuthenticationScheme)
.RequireAuthenticatedUser();
});
})
.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options =>
{
configuration.Bind("AzureAd", options);
});
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
...
}
AzureADDefaults.AuthenticationScheme and AzureADDefaults.OpenIdScheme are now obsolete with message "Use Microsoft.Identity.Web instead. See https://aka.ms/ms-identity-web.". However I can't find any clear documentation how to upgrade following code to use Identity.Web instead of those obsolete constants.
Does anyone have instructions how to remove this obsolete code?
This blog shows you the differences between Identity Platform and Identity.Web.
For Identity.Web, we use Microsoft.Identity.Web and Microsoft.Identity.Web.UI. Try to see this sample, and it uses AddMicrosoftIdentityWebAppAuthentication to sign in users.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
// Handling SameSite cookie according to https://learn.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1
options.HandleSameSiteCookieCompatibility();
});
// Sign-in users with the Microsoft identity platform
services.AddMicrosoftIdentityWebAppAuthentication(Configuration);
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.AddRazorPages();
}
Related
I want to use Azure AD and Azure B2C in one application. I registered both authentication schemes which works fine.
Now I want to allow the user to decide which scheme should be used by clicking a "login with AD" or "login with B2C" button. Clicking on one of the buttons should redirect the user to the correct login.
I'm able to do this for AD by using the link MicrosoftIdentity/Account/SignIn. To do this, it's necessary to use services.AddControllersWithViews().AddMicrosoftIdentityUI()
So, how do I get a link like above for B2C?
Here is my code:
public static void AddAzureADAuthenticationApp(this IServiceCollection services, IConfigurationSection configuration)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
configuration.Bind(options);
options.Events.OnTokenValidated = async context =>
{
await AuthorizationHelper.ValidateAADAppToken(context);
};
})
.EnableTokenAcquisitionToCallDownstreamApi().AddInMemoryTokenCaches();
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder(OpenIdConnectDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.AddRequirements(new AzureADAuthorizationRequirement()).Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.AddMicrosoftIdentityUI();
services.AddAuthorization(config =>
{
var policy = new AuthorizationPolicyBuilder(OpenIdConnectDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.AddRequirements(new AzureADAuthorizationRequirement()).Build();
config.AddPolicy(Constants.PolicyInternalUsers, policy);
});
}
public static void AddAzureB2CAuthenticationApp(this IServiceCollection services, IConfigurationSection configuration, bool RequireAccountNum = false)
{
services.AddAuthentication()
.AddMicrosoftIdentityWebApp(options =>
{
configuration.Bind(options);
options.SignInScheme = Constants.B2CAuthenticationScheme;
options.ResponseType = "code id_token";
options.Scope.Clear();
options.Scope.Add("https://graph.microsoft.com/openid");
options.Events.OnTokenValidated = async context =>
{
await AuthorizationHelper.ValidateB2CAppToken(context, RequireAccountNum);
};
}, openIdConnectScheme: Constants.B2CAuthenticationScheme, cookieScheme: Constants.B2CCookieScheme)
.EnableTokenAcquisitionToCallDownstreamApi().AddInMemoryTokenCaches();
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder(FischerLib.Extensions.Constants.B2CAuthenticationScheme)
.RequireAuthenticatedUser()
.AddRequirements(new AzureB2CAuthorizationRequirement(RequireAccountNum)).Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.AddMicrosoftIdentityUI();
services.AddAuthorization(config =>
{
var policy = new AuthorizationPolicyBuilder(FischerLib.Extensions.Constants.B2CAuthenticationScheme)
.RequireAuthenticatedUser()
.AddRequirements(new AzureB2CAuthorizationRequirement(RequireAccountNum)).Build();
config.AddPolicy(Constants.PolicyExternalUsers, policy);
});
}
At the moment I'm using two controller which have authorize attributes restricted to the scheme. Accessing these controller redirects the user to the login.
But this is not the way which I want to use. After the code above is part of a library and I would like to avoid having to ad the controller in every project.
I know that there is a property LoginPath if I use a different scheme like cookies but I can't set this property in AddMicrosoftIdentityWebApp(...)
Thank you very much!
Markus
Please check this blog on Building a Web Application that Supports both Azure AD and Azure AD B2C - MikaBerglund.com
which works on how to enable switching between Azure AD and Azure AD
B2C just by changing the configuration i.e;app settings.json file
where authority , clientId changes.
The controller actions need to route to selected auth schemes when required.
The way you have added controller seems to be the way.
You can raise a support request for the same.
Reference:
github ref
I am using AzureAD in asp.net core 2 app. I want to use cookie and bearer authentication both. I have following code in startup file:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
//options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options))
.AddAzureADBearer(options => Configuration.Bind("AzureAdClient", options));
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), sqlServerOptions => sqlServerOptions.CommandTimeout(120)));
//services.AddMvc();
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
//options.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
I have added authorized attribute as:
[Authorize(AuthenticationSchemes = "AzureADBearer")]
Now when hitting from postman, i can get the bearer token, but when i am using that token to access this API, i am getting signature invalid error:
WWW-Authenticate →Bearer error="invalid_token", error_description="The signature is invalid"
Any Ideas?
Try something like below , It should work.
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultChallengeScheme = AzureADDefaults.AuthenticationScheme;
sharedOptions.DefaultAuthenticateScheme = AzureADDefaults.AuthenticationScheme;
})
.AddAzureAD(options => Configuration.Bind("AzureAd", options))
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
and in controller application, you can set the schema like this:
[HttpGet]
[Authorize(AuthenticationSchemes = "AzureADBearer")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Hope it helps.
Well, I solved this. Now in asp.net core web + API project, I am using only the API specific AzureAD setting. For Postman and mobile application, I have created a new app registration and added scope for earlier app registration (API app registration), which have user impersonation and access as user permissions.
I am building an intranet site that uses core web api as the backend and angular as the front end. Because the way the db and overall project structure was written, I have an unconventional way of authorizing users. I am grabbing the windows login name (not using identity or any login page), then comparing it to a list of authorized users I have in my db. I got the authorization handler working, however I am stuck on finding a way to prevent my policy from redirecting to a login page (none will exist). Instead of redirecting, I want to just get the 401 status code, so I can use Angular to do a notification
I've done various searches on google/ stack overflow, all the examples and solutions use either identity or token policies, I am not going that route, I am only using a fake cookie auth just to get my authorization policy to work
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper();
services.AddScoped<IChecklistRepository, ChecklistRepository>();
services.AddCors(o => o.AddPolicy("Angular", b=>
{
b.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
}));
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
opt =>
{
opt.LoginPath = null;
opt.AccessDeniedPath = null;
// Does not do anything
});
services.AddDbContext<SWAT_UpdateChecklistsContext>(opt => opt.UseMySql(Configuration.GetConnectionString("conn")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddJsonOptions(o =>
{
o.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddAuthorization(opt =>
{
opt.AddPolicy("AccessUser", policy => {
policy.Requirements.Add(new UserAccess());
});
});
services.AddTransient<IAuthorizationHandler, AuthorizedUser>();
}
I did a little more digging and think I found my answer,
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
opt =>
{
opt.Events.OnRedirectToLogin = ctx =>
{
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
opt.Events.OnRedirectToAccessDenied = ctx =>
{
ctx.Response.StatusCode = StatusCodes.Status403Forbidden;
return Task.CompletedTask;
};
});
I have an ASP.Net Core 2 API using IdentityServer4. I would like challenge ALL requests to the server and invoke the login redirect if the user is not authenticated, calling back to a specific URL after authentication.
The default is to invoke the login redirect only when an unauthenticated user requests a resource protected by the [Authorize] attribute. This will not work in my use case.
Basically, I want the functional equivalent of an [Authorize] attribute for the whole application not just specific controllers.
What is the easiest way to do this? Is there a setting I can use when configuring the services in Startup.cs (services.AddAuthentication)? Or through custom middleware right after app.UseAuthentication()?
I tried the following custom middleware but it says a handler is not configured.
ConfigureServices
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:4000";
options.ApiName = "myapi";
});
Configure
app.UseAuthentication();
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync(IdentityServerAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties {RedirectUri= "https://localhost:5000/" });
}
else { await next.Invoke(); }
});
app.UseMvc();
For configuring [Authorize] for the whole controllers, you could try AuthorizeFilter like below
services.AddMvc(config => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
For redirecting, you could try configure UserInteraction.LoginUrl
services.AddIdentityServer(opt => {
opt.UserInteraction.LoginUrl = "/Identity/Account/LogIn";
})
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<IdentityUser>();
I've setup IdentityServer4, a protected API (Core) project, and various clients. Anonymously accessed client pages use the Resource Owner flow to access the APIs and user credentials are used from client pages where login is required. This is all working. My problem is now I want to add registration API methods that are protected.
The new registration methods require the API project to use AspNetIdentity. Specifically they use Identity's UserManager object which is failing to instantiate unless I add this code to my Startup.ConfigureServices:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
But adding this code breaks the normal IDServer4 Bearer authentication. The Authorize tag on the API controller now sends the requesters to the login page. Is there a way to create a good userManager component without the chunk of code above so Identity authentication does not come into play?
Without the code above I get the following error:
Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[TestAPICore.Models.ApplicationUser]' while attempting to activate *controller*
Here is my ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
//// ADDING THIS CAUSES API requests to send back the login screen
//services.AddIdentity<ApplicationUser, IdentityRole>()
// .AddEntityFrameworkStores<ApplicationDbContext>()
// .AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}
Ideas for making this work?
Update:
Reading more, it looks like calling services.AddIdentityCore<ApplicationUser>(cfg => {}); is the way to go. I've tried it both before and after the .AddAuthentication code but I still get nearly the same error:
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore`1[TestAPICore.Models.ApplicationUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.UserManager`1[TestAPICore.Models.ApplicationUser]'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites...
...which is slightly different since it no longer references my controller.
Solved!
Here's what worked...
IdentityBuilder builder = services.AddIdentityCore<ApplicationUser>(options => { });
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>();
Thanks,Kirk, for pointing me in the right direction!
So that others may benefit, here's my whole ConfigureServices method for my API project which can manage users but still authenticates against IdentityServer:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
services.AddCors(o => o.AddPolicy("MyPolicy", bld =>
{
bld.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// *** The Fix ***
IdentityBuilder builder = services.AddIdentityCore<ApplicationUser>(options => { });
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}