Hide newly released application temporarly for users in PROD - authentication

We use SSO for autentication of our users. Now we have released a new application only for pilot-testers to our production environment which uses SSO as well. The problem is if other users know the URL could log on to the new application, if they are already logged on to one of our applications.
How do we solve this that only pilot-testers can log on into the application?

What you should do is short-circuit the pipeline when an invalid or unknown user wants to access the application. You can accomplish this with middleware or by adding a filter to the authorization component.
The easiest way may be to use Claim-based authorization for that. You'll only need to add a policy that looks for the presence of a claim.
The startup of the client could look something like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
// this sets up a default authorization policy for the application
// in this case, authenticated users are required
// (besides controllers/actions that have [AllowAnonymous])
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireClaim("http://mynewapp.com/pilot-tester")
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.MapAll();
options.Scope.Add("mynewapp");
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
});
}
This will only grant access to pilot-testers. Please note that all code where the AllowAnonymous attribute is used, still will be available for everybody!
If you want to prevent access to these methods then you'll need to check the user with code, e.g.:
if (User.Identity.IsAuthenticated &&
!User.HasClaim(c => c.Type == "http://mynewapp.com/pilot-tester"))
return Redirect("...");
How to configure IdentityServer:
When your app only is a website without other api's, then you'll need to add the claim to the Identity.
In the database make sure the following records are added (the values are examples):
AspNetUserClaims - add a claim for each user that is a pilot-tester. The type should be something you can use for the filter, like http://mynewapp.com/pilot-tester and value true.
IdentityResources - mynewapp. Corresponds with the requested scope.
IdentityClaims - http://mynewapp.com/pilot-tester (linked to IdentityResource mynewapp).
How this works:
The user is a resource with claims. In order to keep tokens small the claims are filtered by the claims that are part of the requested scopes: openid, profile and mynewapp.
All claims that match by type are included to the User.Identity.Claims collection, that is being used when testing the policy.
If you are using an API then you should protect that resource as well. Add a record to ApiResources Api1. The client application should request the scope:
options.Scope.Add("api1");
Please note that in this case ApiResource and ApiScope have the same name. But the relation between ApiResource and ApiScope is 1:n.
Add a record to the ApiClaims table (or ApiScope to narrow it):
ApiClaims - http://mynewapp.com/pilot-tester (linked to ApiResource Api1).
The user resource remains the same, but now IdentityServer will add the claim to the access token as well. Register the policy in the api in the same way as above.
Being temporary you may want to make the filters conditional, giving you the option to enable / disable the filter.
But you may not have to code at all. Being behind a proxy means that you can look at the filter options there first. You may want to filter on ip adress. This means that you can grant access to everybody from certain ip addresses, without having to change the application.

Related

multitenant not loading parameters

this is my code and it is not working:
services.AddMultiTenant<SampleTenantInfo>()
.WithConfigurationStore()
.WithRouteStrategy()
.WithPerTenantAuthentication()
.WithPerTenantOptions<OpenIdConnectOptions>((options, tenantInfo) => {
//options.ResponseType = tenantInfo.ResponseType;
options.Authority = tenantInfo.OpenIdConnectAuthority;
options.ClientId = tenantInfo.OpenIdConnectClientId;
options.ClientSecret = tenantInfo.OpenIdConnectClientSecret;
options.ResponseType = "code";
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Prompt = "login consent"; // For sample purposes.
});
what I'm trying to solve: this is meant to be an application that allows multiple logins but one per user, so I have several tenants, and the user is meant to select one of them and then the idea is that there is an openidconnect that is set per tenant, so the user gets redirected to login to the selected tenant.
What I have done:
in the beginning, my code didnot include the withpertenantoptions, and I could only login in one of the tenants, but not in the other one, debugging it I saw that ResponseType was not set, so I added withpertenantoptions, to set up just the response type but the rest of the parameters were not loaded, then I added the rest of the parameters depending on the tenant but they are still coming null. They are kept in a appsettings.file.
I am using Finbuckle to deal with the multitenant
The problem was simple, I was setting up oidc options twice, so I was taking the bad ones, solution is to remove {
options.Prompt = "login consent"; // For sample purposes.
}
and then set up everything in the other options

ASP.NET Core 3.1 web application use authorization for multiple areas using different authentication types

I have an ASP.NET Core 3.1 application which follows domain driven architecture and it has 2 areas, one for admin and other one for customers (application users).
I want to enable authentication and authorization for each area separately. For example use Identity 4 for the customer area and cookie base authentication for admin area. But it should be done using a single database and role base authentication should not used to separate areas.
What is the best approach to follow. For example "Multiple authentication scheme", Or any other method.
When it comes to login for admin and customer you can implement it by using acr_values (see definition in spec). Identity server can decide how to authenticate based on acr_values, for example if you provided admin_login as acr_values, then based on that Identity Server will authenticate user (use different identity provider or different database/table).
Your application needs to know whether user wants to login as customer or admin before you redirect to identity server authorize endpoint. In order to know that you will have to implement different authentication schemes in your application (one for admin and one for customer). Once you know user login type, you can add correct acr_values. Below code is not tested but it should give you an idea on how to implement it.
services.AddAuthentication(options =>
{
options.DefaultScheme = "CustomerCookie";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("CustomerCookie", options =>
{
options.Cookie.Name = "CustomerCookie";
options.ForwardChallenge = "oidc";
})
.AddCookie("AdminCookie", options =>
{
options.Cookie.Name = "AdminCookie";
options.ForwardChallenge = "admin-oidc";
})
.AddOpenIdConnect("oidc", options =>
{
// Configure all other options needed.
options.SignInScheme = "CustomerCookie";
options.CallbackPath = "/signin-oidc-customer";
options.Events.OnRedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.SetParameter("acr_values", "customer_login");
return Task.FromResult(0);
};
})
.AddOpenIdConnect("admin-oidc", options =>
{
// Configure all other options needed.
options.SignInScheme = "AdminCookie";
options.CallbackPath = "/signin-oidc-admin";
options.Events.OnRedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.SetParameter("acr_values", "admin_login");
return Task.FromResult(0);
};
});
On identity server side you have full control on what to do based on acr_values, you can use external provider for admin.
You could use IIdentityServerInteractionService.GetAuthorizationContextAsync to retrieve acr_values and you could implement IProfileService so that once authenticated, you can decide what claims to include based on user type (admin or customer).
That would be the basic idea, hopefully it is useful.

Validate that scope is present

I have a ASP.NET Core MVC Project. Authentication is performed using an oidc identity provider.
The client requests a scope "myscope". This scope is added to the access token.
.AddOpenIdConnect(options => {
...
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
// options.ClaimActions.MapJsonKey("scope", "scope");
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("myscope");
options.Events.OnTicketReceived = (x) => {
var props = x.Properties;
// here i can inspect the access token and see the scope is present
return Task.CompletedTask;
};
});
As well i can retrieve the access token from the HttpContext to eventually pass it to an api. But at the moment i want to access my database directly.
Thus i thought i want to validate that the scope "myscope" is present. I want to achieve that using a policy.
services.AddMvc(options => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireClaim(JwtClaimTypes.Scope, "myscope")
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
The authorization unfortunately fails. The reason is, that the claims from the access token are not mapped into the User Claims Principal. MapJsonKey does not help here as well.
How can i check if a scope (of the access token) is present using authorization policies?
I am as well thinking about, if i am trying a pointless approach. I am using Identity Server 4 as my identity provider. The scope is specified as an api resource. Maybe you could argue that the scope should be an identity resource, thus being present in the id token and therefore mapped to the ClaimsPrincipal.
by default you need to be explicit and tell which claims you want from the tokens and user-info endpoint to end up in the user object (Claims principal), by adding this to the AddOpenIDConnect options
options.ClaimActions.MapUniqueJsonKey("website", "website");
options.ClaimActions.MapUniqueJsonKey("myscope", "myscope");
options.ClaimActions.MapUniqueJsonKey("birthdate", "birthdate");
You should also add a IdentityResource definition, because it controls what goes into the ID-Token , like
_identityResources = new List<IdentityResource>()
{
new IdentityResources.OpenId(),
new IdentityResources.Email(),
new IdentityResources.Profile(),
new IdentityResources.Address(),
"myscope"
};

Add claims to access token depending on clients

I have the situation when one web app needs access token with one claim and another web app needs another claim.
For example, client_1 should have access token with claim is_admin and client_2 should have claim stores.
What I want to know - is it normal to add such information in such a manner in access token, or is there a better alternative?
And if to add this info in token - can you suggest how to distinguish which claim to add in IProfileService implementation depending on a client? Is it good to use ClientProperties for this purpose?
UPDATE: I use Azure AD as external identity provider and users don't have those claims - I need to retrieve it from other sources
In your configuration add an IdentityResource that represents the scope of the client app, like client_1_scope, including the IdentityClaim is_admin.
Do the same for client_2_scope including IdentityClaim stores.
Also allow the client to request the defined scope (add a record in ClientScopes).
In client_1 request the scope client_1_scope like this:
options.Scope.Add("client_1_scope");
And in client_2 like this:
options.Scope.Add("client_2_scope");
When the user has a claim is_admin or stores, then the claim will be included as part of the requested scope only.
Add this line to make sure the claims are added:
options.GetClaimsFromUserInfoEndpoint = true;
In the client_1 app the configuration could look something like this:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services
.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.MapAll();
options.Scope.Add("client_1_scope");
options.Authority = "";
options.ClientId = "";
options.ClientSecret = "";
options.ResponseType = "code id_token";
});
The information will be saved in the cookie. If you want claims to be part of an access token that is send to an Api then you should use the Api~ tables to configure the claims.
If those other sources already exist then you can implement IProfileService where you can add the dynamic claims, based on the scope as described above, so you still need to request the scope.
Something like:
using IdentityServer4.Models;
using IdentityServer4.Services;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
public class MyProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// Include configured claims.
context.AddRequestedClaims(context.Subject.Claims);
// The service gets called multipe times. In this case we need
// the UserInfo endpoint because that's where the scope is defined.
if (context.Caller == "UserInfoEndpoint")
{
// Get the value from somewhere and transform to a list of claims.
// You can filter by requested scopes
List<Claim> userClaims = GetUserClaims(context.RequestedResources.IdentityResources);
if (userClaims.Any())
context.IssuedClaims.AddRange(userClaims);
}
}
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
}
}
If you want to add claims to an access token (used for api's) then context.Caller is ClaimsProviderAccessToken and you should look at context.RequestedResources.ApiResources.
Register the service in startup:
.AddProfileService<MyProfileService>()
Please note, this is just an example. I didn't test the code.
Having a seperate source, you can also look at PolicyServer. In that case you can keep IdentityServer for authentication and the PolicyServer for 'opt-in' authorization.

IdentityServer4 authenticate each client separately

I use two different clients. The IdentityServer4 provides API protections and log in form. Can I configure clients to avoid single sign on. I mean that even if I logged in the first client I need to log in the second client too.
My ID4 configuration:
internal static IEnumerable<Client> GetClients(IEnumerable<RegisteredClient> clients)
{
return clients.Select(x =>
{
var scopes = x.AllowedScopes.ToList();
scopes.Add(IdentityServerConstants.StandardScopes.OpenId);
scopes.Add(IdentityServerConstants.StandardScopes.Profile);
scopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess);
var client = new Client
{
ClientId = x.Id,
ClientName = x.Name,
AllowedGrantTypes = GrantTypes.Hybrid,
RequireConsent = false,
RefreshTokenExpiration = TokenExpiration.Sliding,
RefreshTokenUsage = TokenUsage.ReUse,
ClientSecrets = {new Secret(x.Secret.Sha256())},
RedirectUris = new[] {$"{x.Url}/signin-oidc"},
PostLogoutRedirectUris = new[] {$"{x.Url}/signout-callback-oidc"},
UpdateAccessTokenClaimsOnRefresh = true,
AllowAccessTokensViaBrowser = true,
AllowedScopes = scopes,
AllowedCorsOrigins = {x.Url},
AllowOfflineAccess = true
};
return client;
});
}
All client have the same register code (Maybe it is a problem):
const string oidcScheme = "oidc";
const string coockieScheme = CookieAuthenticationDefaults.AuthenticationScheme;
services.AddAuthentication(options =>
{
options.DefaultScheme = coockieScheme;
options.DefaultChallengeScheme = oidcScheme;
})
.AddCookie(coockieScheme)
.AddOpenIdConnect(oidcScheme, options =>
{
options.SignInScheme = coockieScheme;
options.Authority = identitySettings.Authority;
options.RequireHttpsMetadata = false;
options.ClientId = identitySettings.Id;
options.ClientSecret = identitySettings.Secret;
options.ResponseType = "code id_token";
options.Scope.Add("offline_access");
foreach (var scope in identitySettings.Scopes)
{
options.Scope.Add(scope);
}
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
any help will be useful.
As long as you are in the same browser session, and your apps are having the same authority (are using the same Identity Server) this will not work.
I'll explain you why - once you log in from the first client, Identity Server creates a cookie (with all the relevant data needed in it).
Now comes the second client - the authority (the Identity Server) is the same that has issued the cookie. So Identity Server recognizes your session, sees that you are already authenticated and redirects you to the second client, without asking for credentials.
After all, this is the idea of Identity Server:
IdentityServer4 is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core 2.
It enables the following features in your applications:
Authentication as a Service
Centralized login logic and workflow for all of your applications (web, native, mobile, services). IdentityServer is an officially certified implementation of OpenID Connect.
Single Sign-on / Sign-out
Single sign-on (and out) over multiple application types.
and more....
This is from the official documentation.
You have to either go for different authorities (Identity Server instances) for each client, or re-think is Identity Server the right solution for you in this case.
NOT RECOMMENDED
I'm not recommending this, because it kind of overrides the SSO idea of Identity Server, however if you still want to do it then - you can achieve what you want if you override the IProfileService. There is a method public Task IsActiveAsync(IsActiveContext context) and this context has a property IsActive which determines if the current principal is active in the current client.
You can try and implement some custom logic here, and based on the user ID (context.Subject.GetSubjectId()) and the client id (context.Client.ClientId) to determine if the user is already logged in this client or not.
EDIT
After your comment - this is something that doesn't come OOTB from Identity Server (if I can say it like this), but luckily you have an option.
Policy based authorization per client. Like this, your user can authenticate against Identity Server (and all of its clients), but only the specific clients will authorize him. You can treat this policies as a custom authorize attribute (more or less).
Like this, a user will receive unauthorized in clients, where he.. is not authorized. Hope that this clears the thing and helps :)
You can set prompt=login from all your clients.
prompt
none - no UI will be shown during the request. If this is not possible (e.g. because the user has to sign in or consent) an error is returned
login - the login UI will be shown, even if the user is already signed-in and has a valid session
https://identityserver4.readthedocs.io/en/latest/endpoints/authorize.html
This will force the second client to login again regardless of the previous client's login status.