How to use IdentityServer4 to authenticate local user from local database? - asp.net-core

I'm creating SSO solution for multiple existing projects. Most of the applications already use same database, so same users and I use these users in IdentityServer. But there is one app which has its own users DB and login screen. Users from this table are used in one of the tables in this app (FK).
My idea is to leave existing users DB as is. Add a column MasterUserGuid to Users table that will contain "master" user Guid (so the user that IdentityServer uses for authentication) and implement following flow:
User opens the app and is not signed in
User is redirected to IdentityServer and uses global credentials
User is redirected back to the app which gets global user GUID from claims and authenticates local user (with this GUID in the MasterUserGuid column), instead of using global user
The problem is that I don't know how to implement step 3 or if it's even possible/supported in IdentityServer4. At the moment I'm redirected to IdentityServer, am authenticated and redirected back, but then the app tries to use this external user.
While researching I've read that users should be in one table, so maybe this approach is totally wrong and it would be better to remove local users and break FK for mentioned table and do some manual migration of users.
Is the scenario described in the steps I provided possible and sane?

You need to adjust your application to authenticate via IdentityServer first. Remove all the ASP.NET Core Identity logic related to registration, login etc. assuming all of that will be done on IdentityServer side. Then implement the instance of IClaimsTransformation which will either replace your current ClaimsPrincipalor add additional identities to it if needed with the claim values you want (populated from local database). Here is the example:
public class MyClaimsTransformer : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claims = new List<Claim>();
claims.Add(new Claim(...)); // put here your claim type and value
var identity = new ClaimsIdentity(claims);
principal.AddIdentity(identity);
return principal;
}
}
Then register your claims transformer in IOC in Startup.ConfigureServices method:
services.AddTransient<IClaimsTransformation, MyClaimsTransformer>();

Related

IdentityServer4 + Asp.Net Core Identity - Map Identity to application database user

I am trying to implement an IdentityServer4 with Asp.Net Core Identity.
I want to use IdentityServer4 as centralized authentication/authorization point for APIs using always the same identity.
So the idea is to store the Asp.Net Core Identity stuff in an SQL Database which serves as the identity store.
The question now is how to map the centralized identity to application specific data.
I want to use same identity user in several applications, but in each application the user has other related entities, roles etc.
I read through the documentation of IdentityServer4 but could not find anything related to a proposed structure.
As I understood, you somehow map the identity id to your local application user.
Basic data like firstname etc are stored in the centralized identity store and application specific data is stored in the application specific database. So you would not save firstname etc in the application specific db right?
In the every request where you need user specific data will query the identity server to get information/claims?
What about registration process?
Does anybody have a clear structure setup which could be used to understand the whole setup?
(Separation like Asp.Net Identity Provider, IdentityServer4, Protected Api.)
So you would not save firstname etc in the application specific db
right?
Yes, User specific properties should go into the user profile and should be saved in the user store(database) of IdentityServer. Application specific user data should be stored in the application store.
In the every request where you need user specific data will query the
identity server to get information/claims?
Not necessarily, The user specific data can be included in identity token as claims. The claims will then be stored in a cookie as authentication ticket. For each request, those claims(stored in cookie/s) are available via User property of the controller
var identity = (ClaimsIdentity)User.Identity;
IEnumerable<Claim> claims = identity.Claims;
You can store and query application related user data stored against a user Id(sub claim can be used as a user Id).
If you need a lot of user-specific data in an application it is not optimal to include everything in identity-token and it is unlikely that you will need those for each request. So when you need extra information you can query UserInfo endpoint of identity server. Just include basic information you need to identify a user in your identity token.
What about the registration process?
Registration is a completely separate workflow that there is no involvement with identity server. You just need to save the user to identity store(probably using asp.net identity). Of course, you can host the registration controllers along with identity server, so that identity-related stuff is physically on the same server. You can also just write to IdentityServer user store from anyplace you host your registration process(e.g. A separate admin UI, or From a workflow involving email verification, manual approval etc...)
To customize what you store in Asp.net Core Identity, you need to use
services.AddIdentity<ApplicationUser, ApplicationRole>. ApplicationUser and ApplicationRole are extending IdentityUser and IdentityRole. This way you can make it store any extra info you want.
Then to return the extra info you need to create a ProfileService that implements IProfileService. With this service, you can add any extra information to claim tokens.
You need to register this service as
services.AddSingleton<IProfileService, ProfileService>();
builder.AddAspNetIdentity<ApplicationUser>().AddProfileService<ProfileService>();
You can register the user with extra info like below:
var user = new ApplicationUser
{
UserName = Username,
Email = email,
ExtraInfo1 = "Hello",
ExtraInfo2 = "World"
};
await _userManager.CreateAsync(user, "SomePassword");
With OpenId you have default set of claims associated with user. So any client application can access those claims. Make sure each client has openid and profile scopes assigned to them. Otherwise client application not able to access the users basic details.
In Asp.Net Core application you can access those claims in controller using User property.

Can we restrict users in identity server4 to specific applications?

I am trying to implement IdentityServer 4 for enterprise scenario.
I understand that users are registered against Identity server.
My question is how to give permissions to users against applications, like as users are needed to assign to a particular application, if not assigned application should return unauthorized.
If a user needs to access multiple applications then multiple assignments are needed.
I am looking a way for Identity server to invalidate the submitted token if the user doesn't have access to the application in a single go, even though the challenged token might be valid if it is submitted by other application which the user has access to
Identity Server absolutely handles authorizations on the most basic level. It creates authorization codes and access_tokens that are essential in an applications authorization. Without them you cannot get authorized. Thus for others to claim Identity Server does not do authorizations is flat out wrong.
I came in here a week ago looking for a solution for this very same problem. I want to restrict users to specific applications by not granting them access tokens if they fail to meet certain parameters, in my case a UserClient table. Lucky for you I have a solution. Identity Server 4 implements a few, what they call, CustomValidators that occur at the time of authorization or token creation. They are
internal class DefaultCustomAuthorizeRequestValidator : ICustomAuthorizeRequestValidator
internal class DefaultCustomTokenRequestValidator : ICustomTokenRequestValidator
public class DefaultCustomTokenValidator : ICustomTokenValidator
There name really says it when they get called. Each one contains a single method
public Task ValidateAsync(CustomAuthorizeRequestValidationContext context)
{
return Task.CompletedTask;
}
Notice something? That's is right! It does nothing. Almost as if they are meant to be replaced. (It is).
This is the area that you can add your custom logic to reject the request. CustomAuthorizeRequestValidationContext contains ClientId and User claim information. It also contains a boolean value called IsError. Simply set that to true and whamy! Access denied. You can also set error messages etc. Here is an example that implements the ICustomAuthorizeRequestValidator inface that will restrict a user based on there user Id
public Task ValidateAsync(CustomAuthorizeRequestValidationContext context)
{
var sub = context.Result.ValidatedRequest.Subject.FindFirst("sub");
if (sub != null && sub.Value != "88421113")
{
context.Result.IsError = true;
context.Result.Error = "Unauthorized";
context.Result.ErrorDescription = "You are not authorized for this client";
}
return Task.CompletedTask;
}
Feel free to inject a dbcontext or two to read off of your userclient table. I check the sub claim to be null because this will get hit several times before actual login occurs.
From what I noticed all three behave similar in terms of use, but different in terms of outcome. Setting an error ICustomAuthorizeRequestValidator will prevent the redirect to your client and instead direct you to the Identity Server error screen. The other two will redirect back to the client and generally throw some throw some sort of HttpResponse error. Therefore replacing the ICustomAuthorizeRequestValidator seems to work best.
So simply created a class that implements ICustomAuthorizeRequestValidator. Then add that into your identity services like so
services.AddIdentityServer().AddCustomAuthorizeRequestValidator<MyCustomValidator>()
and you are done done.
You can add a claim in your IdentityServer4's claims table called "role" and in your application, add some UI to authorize a person via email or similar, and then set his/her role in the claims db. And you can also delete the authorized user from your application, which should un-assign a role to that particular person. Thus he/she although is successfully authenticated, can't use your application because you have authorized then. Hope this approach helps you!
For users, IdentityServer is authentication only. Authorization should be handled by your application.
Authentication = Verifying who a user is
Authorization = Verify what a user can do
Update
I wrote an article on this topic to clarify how OAuth 2.0 does is not user-level authorization. Hope it helps! https://www.scottbrady91.com/OAuth/OAuth-is-Not-User-Authorization
As Scott says, Identity Server will authenticate that the user is who they say they are, not explicitly tell you what that user can do.
You can use the claims returned as part of that authentication to then perform authorization checks within your app. For example, you might use the sub or id claims to perform checks from your app on whether the user associated with that sub/id is allowed to access a specific resource.
The water gets a bit muddier when you bring role claims into the picture, but so long as you appreciate the difference between authentication and authorization you should be ok.
In our enterprise scenario we split it into layers:
We introduced a tenant -- a customer (organization) of our enterprise
solution.
Then we have roles (not more than 20 or so) assigned for
each particular user.
IdentityServer fetches users from tenant and access APIs. The only pre-check it performs is that a particular client (application), requested a token, is not restricted for the particular tenant (customer-level licensing), otherwise we display a message and block the challenge response.
Then we come to an app. With a valid token, having tenant and roles inside. The roles-to-functions assignment could be unique within the tenant. So the application itself performs a granulate permissions check, using a separate API. The application is free to enable-disable some functions or even redirect to the special page in IdSrv "Access denied for the app".
With such approach we are scalable, we are configurable, we are as fast as we want. In previous generation we had "all in one" identity+access+licensing monster-like system, and we decided to split. Today we do not face any real limits with adding new customers (tenants), having 20000 users in average each.
Another way, you can redirect user back to respective client login page it they are not assigned to application/client by using IProfileService of IdentityServer4.Services
public async Task IsActiveAsync(IsActiveContext context)
{
if (!string.Equals("MyAllowedApplicationId", context.Client.ClientId, StringComparison.OrdinalIgnoreCase))
{
context.IsActive = false;
}
}
You have to set IsActive = false to redirect user back to login page where user can login with user details which is allowed in application

ASP Net MVC Core - Load User Data From Active Directory when User browses Any Page

Here is my development environment:
Intranet Website
Active Directory Authentication/Authorization
Asp Net Core
I am trying to get the data stored in Active Directory attributes when a user enters firstly to any page in our application. All users rights and permissions, employeeid, studentid, etc.... are stored in AD Attributes and Security Groups. Some Attributes need to be displayed on the website too.
Let's say my website got the following urls...
http://mysite/Home/Index
http://mysite/Student/Index
http://mysite/Student/MyJobs
http://mysite/Staff/Applications
etc....
Any users can go onto some areas/urls of the website freely from other Intranet portals and I don't know where should I write the code to fulfill that criteria. The problem is that, there is no specific entry point to the application like http://mysite/Login or Authenticate, etc. If there is, I could load all users details and rights from AD on that single entry point.
In MVC5 era, I used Custom Global Authorize Attribute and put it on the BaseController is inherited from all other controllers to load that AD data. I put the AD's data into Session on the first hit and use the Static Class to display on Views and use in Controllers. But when I did some research in MVC Core, some say that it's outdated and I should use the Authorize Policy instead of custom Authorize Attributes.
Getting the data from Active Directory is already achieved by using my old webservices and we don't need to worry about .Net core not supporting AD yet.
I looked at the tutorials about Policy and saw something about Claims and Custom User Managers. I couldn't decide which one I should use to load data from Active Directory to the object (probably Scoped Object DI) which lasts for the whole user's session.
Should I load the data onto claims attributes
Eg...
var claims = new List<Claim>();
claims.Add(new Claim("UserName", "John.Smith", ClaimValueTypes.String, Issuer));
claims.Add(new Claim("RefNo", "02343001", ClaimValueTypes.String, Issuer));
claims.Add(new Claim("Email", "MyEmail#email.com", ClaimValueTypes.String, Issuer));
Or Should I write customized SignInManager and IdentityUser?
Eg...
public class ApplicationUser : IdentityUser
{
public string RefNo { get; set; }
public string Email { get; set; }
}
Is there anywhere I could put my code to check AD and load data?
And should I store the data in that Claimed Object rather than using Session Data?
Could you guys please advise me? Feel free to criticize if I miss anything and my idea is not working.
You're right in saying there's no System.DirectoryServices yet (it's on the backlog, I promise) so there are a couple of places to do this.
If you're already using Integrated Authentication you have SIDs for group membership, which are resolved when you call IsInRole(), so you can use role based membership (rather than Claims based) to solve basic authentication problems.
However if you want to support a forms based mechanism then you should look at using the cookie middleware, raw, to at least give you a simple login, calling your web service to validate your login. You could query your API in the controller code, and write an identity cookie. This cookie automatically encrypted and signed, so it can't be tampered with.
The problem comes when you want roles, and attributes. If you head down the cookie route you might be tempted to put all of those as claims in the identity before writing the identity out as a cookie. This might work, provided there are not too many - cookies have a maximum size (browser dependent, but under 4k usually). You can used chunked cookies, but there's a performance impact here. Instead you might use a reference cookie, where you put in a reference to another store where the actual fully populated identity is stored, be it session, redis or something else.
Then in the claims transformation middleware you can pull the reference out, go to your store, and rehydrate the identity.
I'd honestly avoid trying to merge all of this into ASP.NET Identity. That's mean to be the sole source for user information in an application, and in your case that's not true. Your sole source should be AD.
There's also a port of Novell's ldap library to core, which should stand in nicely for DirectoryServices should you want to avoid your web services approach.

How to propagate an administrator's changes to a user's claims

Situation
Let's say an administrator of a site removes a user from the Admin role and adds her to the Contributor role. According to the site's database, that user has been demoted and should no longer have access to Admin-only features. Now the user comes back to the site some time after that change, but had logged in sometime before the change and is still logged in. So long as that user does not log out, she will continue to have claims that say she is in the Admin role. If she logs out, or gets logged out, she loses the claim that she belongs to the Admin role and when she signs back in receives the new claim of belonging to the Contributor role.
Desire
What I would like to happen, perhaps the next time the user requests a page from the site after the administrator made the change, is have that user transparently lose the Admin role claim and gain the Contributor role claim without them having to sign out or do anything special. In fact, I would prefer they are unaware of the change, except that her menu has changed a little because she can no longer perform Admin-only activities.
How would you handle this situation in a way that is invisible to the affected user?
My thoughts
I am using ASP.NET MVC 5 and ASP.NET Identity, but it seems like a solution to this could be easily generalized to other claims based frameworks that utilize cookies. I believe that ASP.NET Identity stores claims in the user's cookies by default in MVC 5 apps.
I have read the following post along with many others on SO and it comes closest to answering this question but it only addresses the case where the user updates herself, not when someone else like an administrator makes the change to her account: MVC 5 current claims autorization and updating claims
There is a feature in Identity 2.0 which addresses this, basically you will be able to do something like this which adds validation at the cookie layer which will reject users who's credentials have changed so they are forced to relogin/get a new cookie. Removing a role should trigger this validation (note that it only does this validation check after the validationInterval has passed, so the cookie will still be valid for that smaller timespan.
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider {
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});

How to implement "remember me" using ServiceStack authentication

I am trying to implement a Remember me feature in a ServiceStack-based project. I don't want to use Basic Authentication because it requires storing password in clear text in a browser cookie, so I need to come up with an alternative approach that will be easy to maintain and customized to my existing database.
I understand that ServiceStack's own support for Remember me is based on caching the IAuthSession instance in the server-side cache, which by default is an in-memory data structure that is wiped out when the website restarts (not good). Alternatively, the cache can also be based on Redis or Memcached, which is better (cached data survives website restarts) but adds more moving parts to the picture than I care to add to it.
Instead, I would like to implement the this functionality using my own database:
Table Users:
UserID (auto-incremented identity)
Username
Password
Email
Name
etc...
Table Sessions:
SessionID (auto-incremented identity)
UserID (FK to Users)
StartDateTime
EndDateTime
SessionKey (GUID)
The way I see things working is this:
On login request, AuthService creates an empty instance of my UserAuthSession class (implements IAuthSession) and calls my custom credentials provider's TryAuthenticate method, which authenticates the user against the Users table, populates UserAuthSession with relevant user data and inserts a new record into the Session table.
Then the auth session is cached in the in-memory cache and ServiceStack session cookies (ss-id and ss-pid) are created and sent to the browser.
If the user checks Remember me then additionally my custom credential provider's OnAuthenticate method creates a permanent login cookie that contains the user's username and the auto-generated Sessions.SessionKey. This cookie will help us track the user on subsequent visits even if the auth session is no longer in the cache.
Now, suppose the site has been restarted, the cache is gone, so when our user returns to the site his auth session is nowhere to be found. The current logic in AuthenticateAttribute redirects the user back to the login screen, but instead I want to change the flow so as to to try to identify the user based on my custom login cookie, i.e.:
look up the latest Sessions record for the username extracted from the login cookie
check if its SessionKey matches the key in the login cookie
if they match, then:
read the user's data from the Users table
create my custom auth session instance, fill it with user data and cache it (just like at initial login)
insert a new Sessions record with a new SessionKey value
send back to the browser a new login cookie to be used next time
if the keys don't match then send the user back to the login screen.
Does the above logic make sense?
Has anyone already implemented anything similar using ServiceStack?
If I were to proceed with this approach, what is the best course of action that doesn't involve creating my own custom version of AuthenticateAttribute? I.e. which hooks can I use to build this using the existing ServiceStack code?
This is already built for you! Just use the OrmLiteCacheClient.
In your AppHost.Configure() method, add this:
var dbCacheClient = new OrmLiteCacheClient {
DbFactory = container.Resolve<IDbConnectionFactory>()
};
dbCacheClient.InitSchema();
container.Register<ICacheClient>(dbCacheClient);
I am not sure when this particular feature was added, perhaps it wasn't available when you originally asked. It's available in v4.0.31 at least.