Implement Active Directory Group in Asp.Net CORE 3.x - azure

Asp.net CORE 3.x :
The authentication is working fine with Azure Active Directory.
Now, i would like to implement the authorization a specific AD Group for all routes.
How to implement this authorization ? steps by steps with Asp.NET Core ?
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = AzureADDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = AzureADDefaults.AuthenticationScheme;
}).AddAzureAD(options => Configuration.Bind("AzureAD", options));
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseHttpsRedirection();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute().RequireAuthorization();
//endpoints.MapControllers();
});
}
}
}
Thank you for you help ! :)

You can use groups claims in Azure AD , config the your application in azure portal to receive group claims by editing the manifest :
{
...
"errorUrl": null,
"groupMembershipClaims": "SecurityGroup",
...
}
ID token issued from Azure AD will include the current user's groups id list in groups claim , then in asp.net core application , you can restrict the access by :
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser().RequireClaim("groups", "YourGroupID")
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
Note : From document :
If a user is member of more groups than the overage limit (150 for SAML tokens, 200 for JWT tokens), then the Microsoft Identity Platform does not emit the groups claim in the token. Instead, it includes an overage claim in the token that indicates to the application to query the Graph API to retrieve the user’s group membership.

Related

Azure SignalR Services with Bearer Authentication

We are trying to scale out our calendar application, that uses SignalR to push updates to clients based on their user's OrganizationId. Previously, the SignalR stuff was hosted within the single App Server, but to make it work across multiple servers we have opted to use Azure SignalR Services.
However, when the application uses the Azure solution, autorisation breaks.
Authentication is set up in Startup.cs to look for the token in the url/query-string when dealing with Hub endpoints:
//From: Startup.cs (abridged)
public IServiceProvider ConfigureServices(IServiceCollection services)
var authenticationBuilder = services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = OAuthValidationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OAuthValidationDefaults.AuthenticationScheme;
});
authenticationBuilder
.AddOAuthValidation(options => {
options.Events.OnRetrieveToken = async context => {
// Based on https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.0
var accessToken = context.HttpContext.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/signalr/calendar")) {
context.Token = accessToken;
}
return;
};
})
.AddOpenIdConnectServer(options => {
options.TokenEndpointPath = "/token";
options.ProviderType = typeof(ApplicationOAuthProvider);
/*...*/
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime) {
app.UseAuthentication();
}
When using the Azure SignalR Service, the OnRetrieveToken event code is simply never hit, which makes sense given that the request is no longer directed at the App Service, but instead to the url of the Azure SignalR Service.
This Hub works while SignalR is hosted on the App Server:
[Authorize(Roles = "Manager, Seller")]
public class CalendarHub : Hub<ICalendarClient> {
private IHttpContextAccessor httpContextAccessor;
public CalendarHub(IHttpContextAccessor httpContextAccessor) { this.httpContextAccessor = httpContextAccessor }
public override async Task OnConnectedAsync() {
await Groups.AddToGroupAsync(Context.ConnectionId, GetClaimValue("OrganizationId"));
await base.OnConnectedAsync();
}
private string GetClaimValue(string claimType) {
var identity = (ClaimsIdentity)httpContextAccessor?.HttpContext?.User.Identity;
var claim = identity?.FindFirst(c => c.Type == claimType);
if (claim == null)
throw new InvalidOperationException($"No claim of type {claimType} found.");
return claim.Value;
}
}
But when I switch to the Azure solution:
//From: Startup.cs (abridged)
public IServiceProvider ConfigureServices(IServiceCollection services)
services.AddSignalR().AddAzureSignalR();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime) {
app.UseAzureSignalR(routes => routes.MapHub<CalendarHub>("/signalr/calendar"));
}
...connecting to the hub causes exception No claim of type OrganizationId found. because the identity is completely empty, as if no user was authenticated. This is especially strange, given that I've restricted access to users of specific roles.
Turns out the error is the same as this question where HttpContext is used to get the claim values, because that's what we do everywhere else. And this seems to work as long as it is the App Service itself handling the connection to the client.
But Azure SignalR Service supplies the claims somewhere else:
The correct way is using just Context which has the type HubCallerContext when accessed from a SignalR Hub. All the claims are available from here with no extra work.
So the method for getting the claim becomes
private string GetClaimValue(string claimType) {
var identity = Context.User.Identity;
var claim = identity.FindFirst(c => c.Type == claimType);
if (claim == null)
throw new InvalidOperationException($"No claim of type {claimType} found.");
return claim.Value;
}

.Net Core Web API Basic Authentication Authorize does not work on Azure

I wrote a custom auth handler for a web API in .net core 3.0 following this tutorial by Jason Watmore. The Authorize attribute works great IIS express. However, when I publish the code to Azure Web App the Authorize attribute does not fire. There is no authentication challenge and data is returned without authentication.
Azure Authentication Authorization Settings
Here is the custom BasicAuthenticationHandler
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly IAPIRepo _apiRepo;
public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder,
ISystemClock clock,
IAPIRepo apiRepo): base(options, logger, encoder, clock)
{
_apiRepo = apiRepo;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//throw new NotImplementedException();
if (!Request.Headers.ContainsKey("Authorization"))
return AuthenticateResult.Fail("Missing Authorization Header");
User user = null;
try
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
var username = credentials[0];
var password = credentials[1];
user = _apiRepo.Authenticate(username, password);
}
catch
{
return AuthenticateResult.Fail("Invalid Authorization Header");
}
if (user == null)
return AuthenticateResult.Fail("Invalid Username or Password");
var claims = new[] {
new Claim(ClaimTypes.NameIdentifier, user.User_Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
Startup.cs
services.AddScoped<IAPIRepo, APIRepo>();
services.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Edit: Difference between .net core 2.2 and 3.1. Changing the run time to 3.1 fixed the issue
It looks like you are using the Startup.cs of .NET Core 3.0 instead of 3.1 like the article is using.

Enabling Azure AD auth on one page of a web forms application

I am the dev lead on a web forms project. There are 2 "modes" to the login page, one for customers, and one for admins.
For additional security, we would like to enforce admin users to authenticate with their Azure AD creds.
Is there a way at the application level for a specific page/URL to kick off the Azure AD auth based on a specific URL? We cannot do this from Azure itself as obviously our customers are not in our AD account.
Just mark admin view controllers with [Authorize] attribute and configure Openid authentication from within the app. This means all views served from this controlled class will require user authentication.
There are a lot of examples on the web how to do this. In case of .net core 2.0 here is the sample.
AccountController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
namespace WebApp_OpenIDConnect_DotNet.Controllers
{
[Route("[controller]/[action]")]
public class AccountController : Controller
{
[HttpGet]
public IActionResult SignIn()
{
var redirectUrl = Url.Action(nameof(HomeController.Index), "Home");
return Challenge(
new AuthenticationProperties { RedirectUri = redirectUrl },
OpenIdConnectDefaults.AuthenticationScheme);
}
[HttpGet]
public IActionResult SignOut()
{
var callbackUrl = Url.Action(nameof(SignedOut), "Account", values: null, protocol: Request.Scheme);
return SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
CookieAuthenticationDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme);
}
[HttpGet]
public IActionResult SignedOut()
{
if (User.Identity.IsAuthenticated)
{
// Redirect to home page if the user is authenticated.
return RedirectToAction(nameof(HomeController.Index), "Home");
}
return View();
}
[HttpGet]
public IActionResult AccessDenied()
{
return View();
}
}
}
And in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
services.AddMvc();
}
and in Configure Method add
app.UseAuthentication();
And lastly in appsettings.json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "[Enter the domain of your tenant, e.g. contoso.onmicrosoft.com]",
"TenantId": "[Enter the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
"ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]",
"CallbackPath": "/signin-oidc"
}
}
More here
https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-webapp-openidconnect-aspnetcore/

Where local IIS is saving login and Registering User from Asp.Net Core (2.0 & 2.2)

I create a new clean project, In VS.2017 everything run well, but when i publish into my local Windows 10 IIS
I can register or login user (but no show name or this options:)
HttpContext.User.Identity.Name not working in IIS
SignInManager.IsSignedIn(User) not working in IIS
I look into my Database and users created are not there, but if I tried try recreate them register option say they already exists.
All the code is by default except the connection
appsetting.jon
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=DataCenter;Initial Catalog=ContaOnPrueba;User Id=Pavel;Password=MyPassWord21;MultipleActiveResultSets=True;App=EntityFramework"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
And Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// 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.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
According to your description, I guess you may not enable the windows auth and disable the anonymous auth in the IIS.
I suggest you could open the IIS management console to check the authentication settubg.
Make sure you have enable the windows auth and disable the anonymous auth.
Result:

Activate Azure Ad authentication when you hit https://host:port/swagger on net Core 2 Api?

I make all changes on my api to use Azure Ad with this and this link features, but when the api is deployed, I need to make the user who gets the Url https://myapi.com/swagger (for example) to redirect it to azure Ad login,then know if the client have rights or not to use this api and redirect it again to my api and show the enpoints he have access.
I make some changes on startup.cs to use OpenIdConnect
//Add AddAzureAdBearer Auth options
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = OpenIdConnectDefaults.AuthenticationScheme;
//options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(option =>
{
option.ClientId = Client;
option.Authority = $"{Instance}/{Tenant}";
option.SignedOutRedirectUri = "https://localhost:44308";
option.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
option.SaveTokens = true;
option.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context =>
{
context.HandleResponse();
return Task.CompletedTask;
}
};
})
.AddCookie()
.AddAzureAdBearer(options => _configuration.Bind("Ad", options));
And I add a HomeController to redirect to swagger UI:
[Authorize]
public class HomeController : Controller
{
[HttpGet("")]
public ActionResult Index()
{
return Redirect("~/swagger");
}
}
When I launch the api, it works as spected, but when y write https://{host:port}/swagger it does not work, don't hit the authentication process and goes to https://{host:port}/swagger/index.html automatically.
How can I fix this?
I'm working with net core 2.0 and Swashbuckle for swagger.
You you need to add Swagger support to ConfigureServices(IServiceCollection services) and to Configure(IApplicationBuilder app, IHostingEnvironment env) in your application’s Startup.cs file. To do so, you need to create a SwaggerServiceExtensions class and add the necessary code to support Swagger in your app.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;
namespace JwtSwaggerDemo.Infrastructure
{
public static class SwaggerServiceExtensions
{
public static IServiceCollection AddSwaggerDocumentation(this IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1.0", new Info { Title = "Main API v1.0", Version = "v1.0" });
c.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
});
return services;
}
public static IApplicationBuilder UseSwaggerDocumentation(this IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1.0/swagger.json", "Versioned API v1.0");
c.DocExpansion("none");
});
return app;
}
}
}
Changes in Startup.cs file
Using the above class, the only thing you need to do in your Startup.cs file is the following:
namespace JwtSwaggerDemo
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//... rest of services configuration
services.AddSwaggerDocumentation();
//...
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
//.... rest of app configuration
app.UseSwaggerDocumentation();
}
//.... rest of app configuration
}
}
}
Authorize requests in Swagger UI
Now, when you load the Swagger’s UI address (e.g: https://localhost:44321/swagger/#/), you will see an Authorize button at the top. Clicking on it leads to a modal window, which allows you to authorize your app with a JWT token, by adding Bearer in the value input field.

Resources