Implementing roles with custom storage provider for Identity - asp.net-core

Recently, I was able to successfully implement authentication (SSO with ADFS using WS-Federation) for an app. Now, I am trying to understand and get authorization working, so this question may be unclear.
I'm using this topic to implement roles with custom storage provider for Identity without entity framework.
I've got custom User and Role models set up, along with the custom UserStore and RoleStore that implement the appropriate interfaces. There's also tables for roles ready to be used.
I run into issues when trying to access either an [Authorized] or [Authorized(Roles = "RoleName")]. As expected, the actions require me to authenticate with ADFS, but when I submit correct credentials the login loops a few times and displays the ADFS error page. This problem with ADFS is not present without the role implementation. UserStore and RoleStore does not implement code yet, but the app never tries uses any of their methods.
I tried implementing different options in Startup.cs, some of which I have commented out, and reordering services. Inserting dummy code into the RoleStore didn't help either. Basically, I just want to be able to add role checks from custom storage using Identity. I can get the username of the user at any time after they log in to find their role.
Startup.cs ConfigureServices method is where it's most unclear for me, and probably the most likely place where something is set up incorrectly.
Startup.cs ConfigureServices():
public void ConfigureServices(IServiceCollection services)
{
// Add identity types
services.AddIdentity<User, Role>()
//.AddUserManager<UserManager<User>>() // some other settings I've tried ...
//.AddRoleManager<RoleManager<Role>>()
//.AddUserStore<UserStore>()
//.AddRoleStore<RoleStore>()
//.AddRoles<Role>()
.AddDefaultTokenProviders();
// Identity Services
services.AddTransient<IUserStore<User>, UserStore>();
services.AddTransient<IRoleStore<Role>, RoleStore>();
//for SQL connection, I'll be using a different one (not the one from the link to topic)
//dependency injection
services.AddScoped<ISomeService, SomeService>();
services.AddAuthentication(sharedOptions =>
{
// authentication options...
})
.AddWsFederation(options =>
{
// wsfed options...
})
.AddCookie(options =>
{
options.Cookie.Name = "NameOfCookie";
//options.LoginPath = "/Access/Login"; //app function the same without this
options.LogoutPath = "/Access/Logout";
options.AccessDeniedPath = "/Access/AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromMinutes(120);
options.SlidingExpiration = true;
});
services.AddControllersWithViews();
}

Another way of doing this is to add a custom attribute store to ADFS.
Then the roles etc. that you require from the custom attribute store can be configured as claims.

Related

Can't Authenticate Power Query to ASP.NET Core MVC Web App

Bottom line up front... when I try to connect Power Query from Excel to my app, I get the error described here - We were unable to connect because this credential type isn’t supported for this resource. Please choose another credential type.
Details...
My app was created from the ASP.NET Core Web App MVC template.
It uses the Microsoft Identity Platform for authentication.
// Sign-in users with the Microsoft identity platform
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
builder.Configuration.Bind("AzureAd", options);
options.Events = new OpenIdConnectEvents();
options.Events.OnTokenValidated = async context =>
{
//Calls method to process groups overage claim.
var overageGroupClaims = await GraphHelper.GetSignedInUsersGroups(context);
};
})
.EnableTokenAcquisitionToCallDownstreamApi(options => builder.Configuration.Bind("AzureAd", options), initialScopes)
.AddInMemoryTokenCaches();
It uses Azure AD group claims for authorization.
// Adding authorization policies that enforce authorization using group values.
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Administrators", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Administrators"])));
options.AddPolicy("SuperUsers", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:SuperUsers"])));
options.AddPolicy("Deliverables", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Deliverables"])));
options.AddPolicy("Orders", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Orders"])));
options.AddPolicy("Users", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Users"])));
});
builder.Services.AddSingleton<IAuthorizationHandler, GroupPolicyHandler>();
In addition to the standard MVC UI, it also has an OData controller for API access.
[ApiController]
[Route("[controller]")]
//[Authorize(Policy = "Users")]
[AllowAnonymous]
public class OdataController : Controller Base
From a browser, everything works perfectly. Access is properly controlled by [Authorize(Policy = "Users")].
From Excel, however, it only works if I set the controller to [AllowAnonymous] and use the Get Data > From Web option.
If I try Get Data > From OData Feed, I get an error The given URL neither points to an OData service or a feed. Which is OK as long as Get Data > From Web works.
If I add the authorization policy, I get an error We were unable to connect because this credential type isn’t supported for this resource. Please choose another credential type.
Following the supported workflow here, Power Query is expecting a 401 response with a WWW_Authentication header containing the Azure AD login URL. Instead, it's being sent directly to the Azure login, and therefore the authentication fails. I did try adding the Power Query client IDs to my Azure AD app, but that had no effect.
I have done every search I can think of and am out of ideas. Can anyone help? Thanks!

Can we have both OAuth and Certificate authentication in ASP .NET Core 5?

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.

Add Authentication inside AutoFac ConfigureTenant

I would like to have Tenant Based Authentication on .NET Core App. I'm using AutoFac to build Tenant based Containers.
I was able to create a ServiceCollection and Populate the authentication services. However Authentication fails and getting Unauthorized response for the Tenant.
public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
{
multitenantContainer.ConfigureTenant("80fdb3c0-5888-4295-bf40-ebee0e3cd8f3", containerBuilder =>
{
containerBuilder.RegisterType<DataService>().As<IDataService>().InstancePerDependency();
containerBuilder.RegisterInstance(new OperationIdService()).SingleInstance();
ServiceCollection tenantServices = new();
tenantServices.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = "https://key-cloak.cloudapp.azure.com:8443/auth/realms/test";
options.Audience = "test";
});
containerBuilder.Populate(tenantServices);
});
return multitenantContainer;
}
I was able to fix it myself with the help of this article.
MultiTenant Authentication by Michael McKenna
By default handlers aren’t registered using the default “.UseAuthentication” middleware. The schemes are registered in the middleware constructor before you have a valid tenant context. Since it doesn’t support registering schemes dynamically OOTB we will need to slightly modify it.
We’re going to take the existing AuthenticationMiddleware.cs and just move the IAuthenticationSchemeProvider injection point from the constructor to the Invoke method. Since the invoke method is called after we’ve registered our tenant services it will have all the tenant specific authentication services available to it now.

OpenIdConnect with .NET Core 2.2 MVC towards IdentityServer3 using ScopePolicy. How do you get scopes into user principal?

It seems to set up OpenIdConnect authentication from .NET Core 2.2 to IdentityServer3 I have to setup through generic AddOpenIdConnect() call, and in order for scope policy to work, I have overridden OnTokenValidated, where I parse the access token received, and add the scopes in it to the ClaimsPrincipal object.
I have found no other way of getting scope policy to work. This seems a bit hackish though. Is there a better or simpler way, so I don't need to override events, or at least not parse the access token? It is parsed in the framework anyhow, so I would suspect there were other functionality available to get scopes into the claims principal.
Moving our code from .NET 4.5.2 to .NET Core 2.2, I need to set up authentication towards our IdentityServer3 server in a very different way.
I was hoping new functionality in later framework allowed for simple setup of authentication towards IdentityServer3, but I've found no fitting example.
I saw someone saying that IdentityServer4.AccessTokenValidation NuGet package could work towards IdentityServer3, but only example I've found has been with simple JWT authentication not allowing implicit user login flow.
Consequently, I've ended up using standard ASP.NET Core libraries to set up openidconnect, and then I need to tweak the code to make it work.
Not sure if the code below handles all it needs to, but at least I've gotten where I can log in and use the new web site, and write cypress tests. Any suggestions on how to do this better or simpler would be appreciated.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
// Without this, I get "Correlation failed." error from Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(o => {
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie().AddOpenIdConnect(o =>
{
o.Authority = "https://myidentityserver3.myfirm.com";
o.ClientId = "myidentityserver3clientname";
o.SignedOutRedirectUri = "https://localhost:50011/signout";
o.ResponseType = "id_token token";
o.SaveTokens = true;
o.Scope.Add("openid");
o.Scope.Add("roles");
o.Scope.Add("profile");
o.Scope.Add("customrequiredscopeforapi");
o.GetClaimsFromUserInfoEndpoint = false;
{
var old = o.Events.OnTokenValidated;
o.Events.OnTokenValidated = async ctx =>
{
if (old != null) await old(ctx);
var token = MyCustomAuthUtils.ParseBearerToken(ctx.ProtocolMessage.AccessToken);
foreach (var scope in token.Scopes)
{
ctx.Principal.AddIdentity(new ClaimsIdentity(new[] { new Claim("Scope", scope) }));
}
// Our controllers need access token to call other web api's, so putting it here.
// Not sure if that is a good way to do it.
ctx.Principal.AddIdentity(new ClaimsIdentity(new[] { new Claim("access_token", ctx.ProtocolMessage.AccessToken) }));
};
}
});
var mvcBuilder = services.AddMvc(o =>
{
o.Filters.Add(new AuthorizeFilter(ScopePolicy.Create("customrequiredscopeforapi")));
});
services.AddAuthorization();
}
The first thing is you don't need to manally decode the access token , just use ctx.SecurityToken.Claims in OnTokenValidated event to get all claims included in the token .
I'm not sure why you need to use scope to identify the permission . The scope parameter in the OIDC-conformant pipeline determines:
The permissions that an authorized application should have for a given resource server
Which standard profile claims should be included in the ID Token (if the user consents to provide this information to the application)
You can use role to identify whether current login user could access the protected resource . And the OpenID Connect middleware will help mapping the role claim to claim principle .

Use ASP.Net Core 2 Identity with Azure Ad single tenant authentication

I would like to get help from community for one problem that I don't understand.
I create asp.net core 2 web application and I would like to configure the app to be able to login from the app via aspnetuser table or by using O365 Company account.
Then I followed multiple techniques described on the web included on MSDN website.
The app authentication works fine but Azure add returned : Error loading external login information.
I checked inside the code by generating identity views, the app failed on:
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
await _signInManager.GetExternalLoginInfoAsync(); return null and return the error message.
The application is correctly configured in azure AD and it work from my app if I remove the authentication from the app.
I configured my app middlewares as follow:
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).AddCookie()
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.ValidateIssuer = true;
});
And in configure method I added
app.UseAuthentication();
When I arrive on my login screen app (scaffolded by VS) all seems correct:
Login screen with two possibilities for authentication]:
Error message when i try Azure Active Directory method:
Can someone explain and help me to solve this problem?
Thanks in advance
The solution is to add cookieschemename as externalscheme. Below is sample code block in Startup.cs file.
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => { Configuration.Bind("AzureAd", options); options.CookieSchemeName = IdentityConstants.ExternalScheme; });
Unfortunately I had more or less the exact same problem. Although the Azure sample worked on its own, when I tried to integrate it to an existing application that uses Identity and other external authentication services, I could not get AzureAD to work. The interesting thing is that although in the output window I could see logging messages saying that the login was accomplished.
What I did (and this is more of a workaround rather than an exact solution to the problem) was to abandon using the Microsoft.AspNetCore.Authentication.AzureAD.UI package and I opted to go the longer way and configure OpenID manually for Azure. This article helped me immensely towards that end.
Having said that, I hope someone posts a more direct answer to your question.