IProfileService - Claims in tokens - identityserver4

I am currently implementing an Identity Server solution and I needed some help with the IProfileService and how it works exactly.
It exposes a method called "GetProfileDataAsync". I understand that this is called when IS4 returns a token. So that means the person gets to the login screen, inputs his details, and then before IS4 returns an Identity token and an Access token, this method will get called to add additional claims.
I am currently trying to figure out the best way to implement roles and permissions based authorization. Currently I need to have access to both the permissions and roles that the user has assigned as that is what our existing code does and we are just switching our authentication model to IS4 but keeping the User Management to be as it currently is.
Questions then...
How best do I implement it? I currently have an ApplicationUser class which implements IIdentity. So should I add a list of roles in there and then a list of permissions, and then populate it when I go get it from the DB when the user does a LogIn?
E.G. In this method
ApplicationUser user = await _userRepo.FindByUsername(model.Username);
The alternative is to add each role and each permission as a claim in my UserProfileService, specifically in the method below
public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context)
I read the following
Often IdentityServer requires identity information about users when creating tokens or when handling requests to the userinfo or introspection endpoints. By default, IdentityServer only has the claims in the authentication cookie to draw upon for this identity data. It is impractical to put all of the possible claims needed for users into the cookie, so IdentityServer defines an extensibility point for allowing claims to be dynamically loaded as needed for a user. This extensibility point is the IProfileService and it is common for a developer to implement this interface to access a custom database or API that contains the identity data for users.
With the above situation, as I have implement the IProfileService, does that mean that all claims that are loaded will be automatically returned and put into the Identity/Access token? Does that mean that for every request that is made to the API, my application will be sending in a token (in the cookie) which could get quite big with these claims that include roles and permissions? What is the alternative as the above statement from the IS4 website mentions it is impractical

How best do I implement it? I currently have an ApplicationUser class which implements IIdentity. So should I add a list of roles in there and then a list of permissions, and then populate it when I go get it from the DB when the user does a LogIn?
There is two kind of things here, Roles and Permissions. Roles are data and you can add them to the token and pass to clients and APIs. you can save the Roles in DB any how which fits your design.
To have the roles in the token you need to fetch them in ProfileService and add to token. Sth like this:
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
context.IssuedClaims.AddRange(context.Subject.Claims);
var user = await _userManager.GetUserAsync(context.Subject);
var roles = await _userManager.GetRolesAsync(user);
foreach (var role in roles)
{
context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, role));
}
}
After doing this your token should contains the roles. make sure to verify the token on https://jwt.ms/
But Permissions are more of real time calculation things.We need to decide about permissions on API based on the user info, user's role or any other data available.
For example a user may have the role as delete (means user can delete things). If this user call order API and tries to delete some one else's order it has to be declined. Means delete permission MUST be calculated based on user's ID + user's role + Order's owner ID.
With the above situation, as I have implement the IProfileService, does that mean that all claims that are loaded will be automatically returned and put into the Identity/Access token? Does that mean that for every request that is made to the API, my application will be sending in a token (in the cookie) which could get quite big with these claims that include roles and permissions? What is the alternative as the above statement from the IS4 website mentions it is impractical
Yes the profile service is called whenever IDS4 needs to return claims about a user to a client applications. If you request an identity and access token - it will get called twice (since you might be putting different claims into each token type).
What is the alternative as the above statement from the IS4 website mentions it is impractical
You should just fetch the data that you need - not extra. As I mentioned above permissions should be calculated on the fly and not be in the token.
Also you can use cache in the ProfileService. But if you use cache you are the one responsible to manage in your code.

Related

IdentityServer4: How to set a role for Google user?

I have 3 applications:
An IdentityServer4 API which provides Google authentication and also provides an access token to authorize the resource API.
A simple Resource API which provides some data from DB.
A simple Client in React which have 4 buttons:
Login, for Google auth
Logout
Get data - a simple request with the access token to the Resource API and gets the data from Db
Get user data - returns user profile and token (for debug purpose)
I didn't put any sample code because my problem is not code related, it's knowledge that I'm missing and I ask for guidance.
The workflow is working just fine: the user press the Login button, it is redirected to IdentityServer4 API for Google Auth. From there it is redirected to a Callback Page from the Client and from there to the Index page. I receive the user data and the token, I can request data from the Resource API and it's working.
My problem is: How do I give a Role to the Google Users ?
I don't have users saved in DB. I want three types of Users: SuperAdmin, Admin, Viewer and each of these roles have limited Endpoints which can access.
For limiting their access I saw that I can use Claims-based authorization or Role-based authorization.
So, my question is how ca I give a Google User who wants to login in my app, a specific Claim/Role ? What is the workflow ? I must save it first in DB ? Or there exists a service from Google where I can add an email address and select a Role for that address ?
Thank you very much !
After you get the response from Google in your callback you can handle the user and do what ever you want to do with it. Below are the some typical tasks that you can do in callback that I took from documentation page of identityserver4 link:
Handling the callback and signing in the user
On the callback page your typical tasks are:
inspect the identity returned by the external provider.
make a decision how you want to deal with that user. This might be
different based on the fact if this is a new user or a returning
user.
new users might need additional steps and UI before they are allowed
in.
probably create a new internal user account that is linked to the
external provider.
store the external claims that you want to keep.
delete the temporary cookie
sign-in the user
What I would do is creating an new internal user account that is linked to the external provider and add a role to that user.
If you don't want to save users in db, you can add an extra claim to user in callback method and use that claim in token. and i think this link will help with that.

IdentityServer4 - Calling API from IProfileService implementation

I'm working on an MVC web project which is using IdentityServer4 to authenticate users, the web app then uses an access token provided to a user by IdentityServer (authorization code flow) to call an API. The IdentityServer has been configured to use Azure AD as an external identity provider, which is the primary mechanism for users to login. That's all working great.
Once authenticated, I need to query the web app's database to determine:
If the user account is authorised to login
Retrieve claims about the user specific to the application
The IdentityServer docs (http://docs.identityserver.io/en/latest/reference/profileservice.html) suggest implementing the IProfileService interface for this, which I've done. I want the ProfileService to call the web app's API to retrieve the information about the user to avoid forcing the IdentityServer to need to know about/directly access the database. My problem however, is that calling the API though needs an access token.
Is it possible to retrieve the token for the current user inside the ProfileService's IsActiveAsync / GetProfileDataAsync methods? I can't find solid documentation that identifies if the token is even generated at that point. I'm also a total noob when it comes to authentication/authorization, it's a massive topic!
I had the idea of using the client credentials flow inside the ProfileService to call the API, just to populate that initial token. However, I don't know whether or not that's an absolutely terrible idea... or if there are any better concepts someone could refer me to that I could investigate.
Can anyone point me in the right direction?
Have a look at ITokenCreationService that is part of identityserver4. You can inject that service into your IProfileService implementation and then create a new bearer token with any claims you like.
For example:
protected readonly ITokenCreationService _tokenCreationService;
...
var token = new Token
{
AccessTokenType = AccessTokenType.Jwt,
Issuer = "https://my.identityserver.com",
Lifetime = (int)TimeSpan.FromMinutes(5).TotalSeconds,
Claims = GetClaimsNeededForApiCall()
};
string myToken = await _tokenCreationService.CreateTokenAsync(token);
...
This is not possible to retrieve the access_token for a user within ProfileService.
The profile service is called whenever IdentityServer needs to return claims about a user. This means if you try to generate a token for the user within ProfileService it will call the ProfileService again.

Separate UserStore By Client

I'm using Identityserver4 and ASPIdentity for user management. I want users dont have access to all applications(clients). I'm going to add table like UserClients, which specifies allowed applications for user. I'm interested in your advice, whether it is possible and does not conflict with the principles of SSO?
IdentityServer takes care of client access configuration and user authentication, but not of authorization at client/user level.
Users are not bound to a client. Thus, as you've noticed, all users have access to a client. IdentityServer authenticates the user, regardless which client is used or whether the user logs in at IdentityServer directly.
So it is the client (and also api) that has to authorize the user.
It doesn't matter if the user is already authenticated (SSO) or not. It is the responsibility of the client / api to determine whether the user is authorized. In order to solve this 'problem' they've build the PolicyServer, to decouple authentication and authorization.
By adding middleware (as done in PolicyServer) you can grant or deny a user access.
You can add some discriminator in IdentityServer, e.g. a specific claim type / role, use a seperate authorization server or use resource based authorization, e.g. when a (business) user is present then the user has access.
In the end, authorization is done by the resource that needs to be protected, because only the resource can interpret the claims and business rules.
All you have to do is short-circuit the pipeline in case the authenticated user has no access.
It's perfectly fine if you want to 'disable' SSO. Simply ignore the IdentityServer cookie (in the client), thus forcing the user to login (for that client).
But you should not deny authentication (e.g. return invalid login) when the user has no access to the client / resource.
That's why there is http status 401 Unauthorized and 403 Forbidden. So you can notify the user that it is not authorized to use this client.
Regarding your setup, in your case you can also add claims to the UserClaims table (instead of creating an additional table) to configure the allowed apps, e.g. user A:
http://mycompany/appname = app1
http://mycompany/appname = app2
http://mycompany/appname = app3
Add the ClaimType to the IdentityClaims table and the ApiClaims table in order to make sure the claims make it to the access / identity token.
Add middleware in the client / resource or add business rules (policies) where you check the claimtype and required value. If not present then deny access. E.g. app1 should look for the required claim type http://mycompany/appname with the value 'app1'.

Adding custom claims into AAD token

Is it possible, while acquiring an access_token (Client Credentials grant), to instruct AAD to inject certain custom claims with certain values into the access_token being issued?
I need it to avoid sending extra context information to my service through such a "disconnected" means as HTTP Header for instance. Instead I want the token signed by AAD and containing everything AAD stamps into it by default plus some small pieces of information controlled by the application acquiring the token. All this will help my service to apply proper authorization once this token is received by the service.
I looked at the above, and I am clear that you are not looking for claims augmentation as it was described in the blog.
As I understood, you are looking for the right way to authorized your application using AAD tokens.
If my understanding is correct here is my answer.
It took me quite sometime to remember how I did it before and the caveat was missing the graph permissions for:
Directory.AccessAsUser.All
Directory.Read.All
Directory.ReadWrite.All
Now let me type down the steps one by one, but care less to the order of these steps may not be correct, just do the steps in any order you want.
Step 1: In AD, in the App registration
Register your Web Application,
Copy the Client_ID
Step 2: Go to Expose an API
Add a scope or more (This is what you are going to see as a claim and role in the token)
Add the client Client_ID
Note: this is basically for 2 applications one calling another, but in this example and your case, you have one web application that needs to authorize on itself.
Next: In the API permissions
THIS IS A MUST grant admin consent delegated permissions for MicrosoftGraph
Directory.AccessAsUser.All
Directory.Read.All
Directory.ReadWrite.All
Additionally: Give permission to the scope that you added.
Then: In the App roles:
Add the Application roles
Then: In the Enterprise Applications:
Assign that role to the users or groups that you want to access this.
Finally: In the application configuration file
Update the Client id
You are done.
I hope that was what you were looking for.

IdenitityServer4. How to create an access token from within login page?

I have created an IdentityServer application using IdentityServer4 with my own login page. When the user logs in (or fails to log in because of invalid credentials) I need to create an audit log record of that interaction. My system has a separate web service that handles audit logging. In order to call that service from my login page in my IdentityServer instance I need an access token. Is there a class/method somewhere in IdentityServer4 that I can just call to create an access token? It seems kind of overkill to make a web service call to the token end point in my application instance in order to get the token.
I.e. my IdentityServer application needs to be able to create access tokens for external clients, but it also needs to be able to create them for it's own use to allow it to be able to call an external service like audit logging.
Or am I looking at things all wrong?
Simply inject 'IdentityServerTools' into your class this has the necessary APIs.
The class that creates the access token is DefaultTokenService and it has an accessiblity level of public.
https://github.com/IdentityServer/IdentityServer4/blob/dev/src/IdentityServer4/Services/DefaultTokenService.cs
The relevant method is:
public virtual async Task<Token> CreateAccessTokenAsync(TokenCreationRequest request)
So it seems to be possible.
--
Or am I looking at things all wrong?
I'd probably defer the product owners on that one.
--
But I do find it ironic that you could
Register the IdentityServer as a Client using the client_credentials grant type and allow it access to your Audit Logging Api Resource.
Have the IdentityServer Client obtain an access_token from itself
And then make a call as the IdentityServer Client to Audit Logging Api Resource, which would then turnout around and call back to IdentityServer to validate the access_token it just generated.
That is way other Clients work, so why not itself...
EDIT
Just for documentation, here's what I have based on leastprivilege's instruction:
public AccountController(
IIdentityServerInteractionService interaction,
IClientStore clientStore,
IHttpContextAccessor httpContextAccessor,
ITokenCreationService tokenCreationService,
TestUserStore users = null)
{
_users = users ?? new TestUserStore(Config.Users);
_interaction = interaction;
_account = new AccountService(interaction, httpContextAccessor, clientStore);
_identityServerTools = new IdentityServerTools(httpContextAccessor, tokenCreationService);
}

Resources