Asp.net Core 2 enable multi tenancy using Identity Server 4 - c#

I have an IDP (Identity Server 4) hosted with multiple bindings: auth.company1.com and auth.company2.com
I also have an API protected from that IDP. So in order to access the API I need to get the access token from the IDP. This is configured at startup class at the API level like this:
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://auth.company1.com/";
options.RequireHttpsMetadata = true;
options.ApiName = "atb_api";
});
How can I configure options.Authority dynamically so it allows authority from multiple domains https://auth.company1.com/ and https://auth.company2.com/ ?

I solved this.
At the protecting API level at the startup class I have this configuration:
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://shared-domain-for-every-tenant/";
options.RequireHttpsMetadata = true;
options.ApiName = "atb_api";
});
The magic happens at the IDP level (IdentityServer4), while configuring the IdentityServer I add the option IssuerUri like this:
services.AddIdentityServer(options => {
options.IssuerUri = "https://shared-domain-for-every-tenant/";
})..AddDeveloperSigningCredential() ...other configurations ...
When I navigate to https://auth.company1.com/.well-known/openid-configuration
the returned document is like this:
{
"issuer": "https://shared-domain-for-every-tenant/",
"jwks_uri": "https://auth.company1.com/.well-known/openid-configuration/jwks",
"authorization_endpoint": "https://auth.company1.com/connect/authorize",
"token_endpoint": "https://auth.company1.com/connect/token",
"userinfo_endpoint": "https://auth.company1.com/connect/userinfo",
...
}
Notice the issure is a static url while all the other endpoints are specific to the tenant that made the request. This allows the API to validate the access token and also have different endpoints for each tenant (I need this to show a different login screen for each of them).
Hope it helps someone out there :)

Related

Implement multiple authorization in .net 6 web API

I have auth0 authentication implemented for my webAPIs. But now due to some requirement change few of APIs need to be authorized with another scheme. So I need below specified different authorization schemes to authorize my API
Auth0 scheme (already authorizing api's)
Azure AD B2C
I have implemented Azure AD B2C and which is working fine when used alone but when I am trying to add it enable it with a previous scheme it is causing issues.
public static IServiceCollection AddSecurityPolicy(this IServiceCollection services, ConfigurationManager config)
{
const string ClientPortalScheme = "ClientPortalBearerScheme";
//from https://auth0.com/blog/securing-aspnet-minimal-webapis-with-auth0/
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = config["AuthenticationSettings:Domain"];
options.Audience = config["AuthenticationSettings:Audience"];
}).AddJwtBearer(ClientPortalScheme, ClientPortalScheme, options =>
{
options.Authority = config["AzureADB2CSettings:Domain"];
options.Audience = config["AzureADB2CSettings:Tenant"];
});
//services.AddMicrosoftIdentityWebApiAuthentication(config, "AzureADB2CSettings");
//By default, require an authenticated user
//Only one JWT bearer authentication is registered with the default authentication scheme JwtBearerDefaults.AuthenticationScheme.
//Additional authentication has to be registered with a unique authentication scheme.
//see https://docs.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-6.0
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme,
ClientPortalScheme);
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
return services;
}
This is how my code looks like.
Issue is when I am calling my endpoint it says
Unable to obtain configuration from: 'https://mydomain.auth0.com/.well-known/openid-configuration'.
---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'https://mydomain.auth0.com/.well-known/openid-configuration'.
Please let me know if any information is required int this regard
I tried to reproduce the issue in my environment.
It occurs when app config is not receiving OpenIDmeta data properly
The issue was due to TLS configuration in my case as TLS 1.1 or TLS 1.0 are depreciated.
Please make sure to set TLS to 1.2 or greater.
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
IdentityModelEventSource.ShowPII = true;
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
......
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;;
....
app.UseAuthentication();
app.UseAuthorization();
....
}
Please make sure your backend API refers to /.well-known/openid-configuration
Or check this way.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
MetadataAddress = "http[s]://{IdentityServer}/.well-known/openid-configuration"
...
});
In AzureAdB2c make sure to set the Authority or (Domain and instance ) property correctly .
Authority being the combination of Instance and domain
Appsettings.json
Authority : https://[yourb2ctenant}.b2clogin.com/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0
Also see if Instance can be https://<tenant>.b2clogin.com/tfp/
Domain : <b2ctenant>.onmicrosoft.com
Then the authentication can be carried on successfully:

C# How to share authentication/authorization cookie between projects?

It's a rather question related with conceptual approach.
So I have two projects - in one the Authentication is already implemented and working ok. It's a .net core SPA with OpenID + Cookie (without without ASP.NET Core Identity).
The second project is the REST API that I want to use that cookie to Authorize some of the endpoints so basically SSO.
The biggest challenge to me is I don't know how I should modify second project to "accept" the cookie from first one - I need at least some starting point.
This one is not helping at all: https://learn.microsoft.com/en-us/aspnet/core/security/cookie-sharing?view=aspnetcore-6.0
It's not clear from documentation what I need to do in both projects to make it work.
So what I'm thinking is to copy-paste the services.AddAuthentication(...) from first project to the second one so they can share same Authority - I believe there is no other way that second project knows that cookie is "ours" cookie?
Update:
Here I'm giving code samples - with setup below it keeps trying to redirect me to login as don't treat it's authenticated:
Project A (where user login and we want "generate" cookie for this and other projects):
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options => {
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options => {
options.Cookie.Name = "test";
options.Cookie.Domain = ".localhost";
options.Cookie.Path = "/";
})
.AddOpenIdConnect(options =>
{
options.ClientId = "xxx";
options.ClientSecret = "xxx";
options.MetadataAddress = $"https://login.microsoftonline.com/xxx/v2.0/.well-known/openid-configuration?appid=xxx";
options.Authority = $"https://login.microsoftonline.com/xxx/oauth2/v2.0";
options.ResponseType = "code";
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters = new TokenValidationParameters {
NameClaimType = "name"
};
});
services.AddDataProtection()
.PersistKeysToFileSystem(new System.IO.DirectoryInfo("/cookies"))
.SetApplicationName("SharedCookieApp");
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "test";
options.Cookie.Domain = ".localhost";
options.Cookie.Path = "/";
});
}
Project B (REST API - here we wan't just to "consume" cookie generated in Project A):
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.Name = "test";
options.Cookie.Domain = ".localhost";
options.Cookie.Path = "/";
options.Events.OnRedirectToLogin = context =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
});
services.AddDataProtection()
.PersistKeysToFileSystem(new System.IO.DirectoryInfo("/cookies"))
.SetApplicationName("SharedCookieApp");
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "test";
options.Cookie.Domain = ".localhost";
options.Cookie.Path = "/";
options.Events.OnRedirectToLogin = context =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
});
}
Error I'm getting:
The documentation you referenced actually has everything you need. But it requires some basic knowledge to understand that. To use the cookie set by one app in another you need to ensure two things:
The cookie should be sent to both app
1.1. If apps hosted on the same domain but on different paths, you need to set cookie Path to the common denominator. For example, here the common denominator is /:
https://my-domain.com/app1
https://my-domain.com/app2
Here is /api (but / is also valid)
https://my-domain.com/api/app1
https://my-domain.com/api/app2
1.2 If apps hosted on different domains, they must be subdomains of some common domain. Set cookie domain to the common domain value to share it between subdomains.
For example, here the common domain is .company.com:
https://sub-domain1.company.com
https://sub-domain2.company.com
This is also example of common domain .company.com
https://company.com
https://sub-domain2.company.com
But these 2 domains can't share cookies because they don't have common domain:
https://sub-domain1.company1.com
https://sub-domain2.company2.com
1.3 You can also mix domain and path configuration if your apps hosted on different domains with common sub-domain and different path. For example, here the domain should be .company.com and path /:
https://sub-domain1.company.com/api/app1
https://sub-domain2.company.com/app2
Both app can decrypt the cookie and understand its content.
2.1 If apps hosted on the same machine you can use file storage to persist data protection keys:
services.AddDataProtection()
.PersistKeysToFileSystem("{PATH TO COMMON KEY RING FOLDER}")
2.2 If the apps hosted on different machines you need to use another type of storage so that both app will be able to access it and read protection keys. For example, you can use Amazon KMS service with Amazon.AspNetCore.DataProtection.SSM nuget:
services.AddDataProtection()
.PersistKeysToAWSSystemsManager("/MyApplication/DataProtection");

Identityserver 4 and Ocelot

I'm trying to use Ocelot with IS4 following
https://ocelot.readthedocs.io/en/latest/features/authentication.html
When using
public void ConfigureServices(IServiceCollection services)
{
var authenticationProviderKey = "TestKey";
services.AddAuthentication()
.AddJwtBearer(authenticationProviderKey, x =>
{
});
}
and use "TestKey" in ocelot.json, it throws an error when starting the application
Unable to start Ocelot, errors are: TestKey,AllowedScopes:[] is unsupported authentication provider
Any idea what's wrong? Do I need set up something in particular in my IdentityServer app?
You need to add the options, e.g.:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// base-address of your identityserver
options.Authority = "https://demo.identityserver.io";
// name of the API resource
options.Audience = "api1";
});
More info at: http://docs.identityserver.io/en/latest/topics/apis.html#
You will also need to add an API resource to your Identity Server:
new ApiResource("api1", "Some API 1")
See:
http://docs.identityserver.io/en/latest/topics/resources.html and http://docs.identityserver.io/en/latest/reference/api_resource.html#refapiresource

How to disable External Logins in Identity Core?

I am in the process of integrating a simplefied authentication process into a asp.net core 2.1 application, where users are logging in via the UI by default, but there is also the possibility to aquire a token and call some secured api endpoints to retrieve some data needed for reporting.
The issue I am facing is, that with the default configuration everything works, but adding the token config throws some weird errors.
If I do not add AddCookie("Identity.External"), call to the onGet method at /Identity/Account/Login throws the exception
InvalidOperationException: No sign-out authentication handler is registered for the scheme 'Identity.External'. The registered sign-out schemes are: Identity.Application. Did you forget to call AddAuthentication().AddCookies("Identity.External",...)?
If I do not specify options.DefaultScheme = "Identity.Application"; the user is not successfully signed in.
If I do not add .AddCookie("Identity.External") and .AddCookie("Identity.TwoFactorUserId") the logout process throws the same exception as above.
For the login process, this is simply rectified by removing the line await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);. If I do not use external schemes I do not need to sign out of them, right?
This brings me to my problem: How can I disable external logins and multi factor authentication in Identity Core, so I do not have to add those cookies in the first place? Furthermore, why do I have to specifiy a cookie named "Identity.Application", which is not the case in the default configuration? I'm pretty sure this is just another issue of me not thoroughly understanding the problem at hand, so I am grateful for any clarification on this.
This is my Identity config from the Startup.cs I have also scaffolded out the complete Identity UI with a custom IdentityUser class.
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
var tokenValidationParameters = new TokenValidationParameters
{
/*...*/
};
services.AddAuthentication(options =>
{
options.DefaultScheme = "Identity.Application";
})
//.AddCookie("Identity.External")
//.AddCookie("Identity.TwoFactorUserId")
.AddCookie("Identity.Application", opt =>
{
opt.SlidingExpiration = true;
})
.AddJwtBearer(options =>
{
options.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.TokenValidationParameters = tokenValidationParameters;
options.SaveToken = true;
});
var builder = services.AddIdentityCore<AppUser>(o =>
{
//removed
});
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();

'SignInScheme' option must be provided

I'm creating an ASP.NET 5 MVC 6 app that will use Facebook/Google authentication only. I'm also trying to use the cookie middleware without the whole ASP.NET Identity -- following this article:
https://docs.asp.net/en/latest/security/authentication/cookie.html
So I started with an blank app with no authentication then added the Microsoft.AspNet.Authentication.Cookies and Microsoft.AspNet.Authentication.Facebook NuGet packages in order to have a very minimalistic approach where I don't include anything that I don't need.
I added the following code into Configure in Startup.cs but I'm getting "SignInScheme option must be provided" error. Any idea what I'm missing?
app.UseCookieAuthentication(options =>
{
options.AuthenticationScheme = "MyCookieMiddlewareInstance";
options.LoginPath = new PathString("/Accounts/Login/");
options.AccessDeniedPath = new PathString("/Error/Unauthorized/");
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
});
app.UseFacebookAuthentication(options =>
{
options.AppId = "myFacebookAppIdGoesHere";
options.AppSecret = "myFacebookAppSecretGoesHere";
});
As indicated by the error message you're seeing, you need to set options.SignInScheme in your Facebook middleware options:
app.UseFacebookAuthentication(options => {
options.AppId = "myFacebookAppIdGoesHere";
options.AppSecret = "myFacebookAppSecretGoesHere";
// This value must correspond to the instance of the cookie
// middleware used to create the authentication cookie.
options.SignInScheme = "MyCookieMiddlewareInstance";
});
Alternatively, you can also set it globally from ConfigureServices (it will configure every authentication middleware so you don't have to set options.SignInScheme):
public void ConfigureServices(IServiceCollection services) {
services.AddAuthentication(options => {
// This value must correspond to the instance of the cookie
// middleware used to create the authentication cookie.
options.SignInScheme = "MyCookieMiddlewareInstance";
});
}

Categories

Resources