Identity Server 4 PersistedGrant Not Saving Custom Claims - asp.net-core

I'm using Identity Server 4 with token based authorization. I haven't implemented profiles at this stage, but for now the Identity Server is working with my custom ResourceOwnerPasswordValidator and is correctly persisting the authorization grants in the PersistedGrants table in my db.
The problem is that the db is failing to also save the custom claims I am applying at this stage.
To create the grant, I am doing the following in my password validator:
public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
...
// validate the user using the context, then generate custom claims
var claims = new List<Claim> { new Claim() }; // add claims here
context.Result = new GrantValidationResult(user.Id.ToString(), "password", claims);
return Task.CompletedTask;
}
but when the persisted claim is created in the db, the Data column only contains the default claims:
{
"CreationTime": "2019-08-21T16:38:18Z",
"Lifetime": 2592000,
"AccessToken": {
// audiences, issuer, creation time, etc...
"Claims": [
{
"Type": "client_id",
"Value": "myClient",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
}, {
"Type": "scope",
"Value": "myAPI",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
}, {
"Type": "scope",
"Value": "offline_access",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
}, {
"Type": "sub",
"Value": "16",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
}, {
"Type": "auth_time",
"Value": "1566405497",
"ValueType": "http://www.w3.org/2001/XMLSchema#integer"
}, {
"Type": "idp",
"Value": "local",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
}, {
"Type": "amr",
"Value": "password",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
}, {
"Type": "name",
"Value": "email#domain.com",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
}
],
},
}
I feel like the solution is overloading the grants class to be sure the data property is properly initialized, but am new to IDServer4 and don't know how to do this.

I ended up implementing my own IProfileService and providing it as a transient resource in my ID4 startup. The GetProfileDataAsync function implemented by the interface allows you to add claims to the user profile that you want accessible as claims on the authorization side.
I still think claims added in the password validator should be passed without additional code. Requiring an additional profile service to add them twice seems redundant.

Related

Adding Custom Claims To Azure Active Directory Web App Login

I have managed to change my .NET Core 6 Razor Pages app to login using Azure Active Directory by following this
https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-web-app-sign-user-sign-in?tabs=aspnetcore
The trouble is that I need to add some custom claims to the login, the details of which are in the database (SQL Server), and I do not know how to go about that other than to store the claims in memory.
Previously, I used the following code in my login page.
public ActionResult OnPostLogin(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
if (_userRepository.GetUserValid(Input.Username, Input.Password))
{
var claimsIdentity = new ClaimsIdentity(_loginClaimRepository.ClaimList(Input.Username), CookieAuthenticationDefaults.AuthenticationScheme);
var result = HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = new DateTimeOffset(DateTime.UtcNow.AddHours(8)),
AllowRefresh = true
});
if (result.IsCompletedSuccessfully)
{
return LocalRedirect(returnUrl);
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
}
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
}
}
return Page();
}
I wonder if there is a standard way to intercept the login process and add some custom claims?
I registered an app in azure active directory by clicking new registration:
Added the name and clicked on azure register.
Image for reference:
After app registration I go to manifest and updated the app roles.
Image for reference:
added app roles in Json format:
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"description": "Consumer apps have access to delete consumer data.",
"displayName": "webapp Delete role",
"id": "5e491592-0270-40cb-b70d-2f67b3ce0910",
"isEnabled": true,
"value": "webapp.delete"
},
{
"allowedMemberTypes": [
"Application"
],
"description": "Consumer apps have access to update consumer data.",
"displayName": "webapp Update role",
"id": "d7395cab-0ae9-41c1-a5cd-e945afca8465",
"isEnabled": true,
"value": "webapp.update"
},
{
"allowedMemberTypes": [
"Application"
],
"description": "Consumer apps have access to write consumer data.",
"displayName": "webapp Writer role",
"id": "37605316-22ca-4587-8a98-56e31ba1a2b0",
"isEnabled": true,
"value": "webapp.write"
},
{
"allowedMemberTypes": [
"Application"
],
"description": "Consumer apps have access to read consumer data.",
"displayName": "webapp Reader role",
"id": "12eb6844-6ff4-4731-a349-d5f803cfd6c5",
"isEnabled": true,
"value": "webapp.read"
}
],
Image for reference:
Saved the update of manifest by clicking save button.
Set the application Id URI. This value will be used as the Scope parameter when requesting an OAuth token for the Client App.
Image for reference:
In this way we can add custom claims app which is registered in active directory.

Authenticating via Okta that uses 2FA

I built a simple asp.net core application and need to authenticate an account that uses MFA in addition to username & password.
The service I'm trying to log into exposes an authentication endpoint described here:
https://developer.okta.com/docs/reference/api/authn/#request-example-for-primary-authentication-with-public-application
When I provide the username and password it returns the expected JSON:
{
"stateToken": "00zQZJ1ZY3Em2401r4T69Dhx35TdF1VTFP5KRTa8ib",
"expiresAt": "2020-05-30T21:11:14.000Z",
"status": "MFA_REQUIRED",
"_embedded": {
"user": {
"id": "00u10vk1torIgBBxxxxx",
"profile": {
"login": "john.doe#email.com",
"firstName": "John",
"lastName": "Doe",
"locale": "en",
"timeZone": "America/Los_Angeles"
}
},
"factors": [{
"id": "ost1g59468dXOddAE0h8",
"factorType": "token:software:totp",
"provider": "OKTA",
"vendorName": "OKTA",
"profile": {
"credentialId": "john.doe#email.com"
},
"_links": {
"verify": {
"href": "https://company.okta.com/api/v1/authn/factors/ost1g59468dXOddxxxxx/verify",
"hints": {
"allow": ["POST"]
}
}
}
}, {
Does anyone know who I can also add 2FA into the request to get it to work?

IdentityServer4 read client claims from appsettings.json

how can I read client claims from the appsettings.json file?
I have this appsettings.json:
"IdentityServer": {
"Clients": [
{
"Enabled": true,
"ClientId": "client1",
"AlwaysSendClientClaims": true,
"Claims": [
{
"Type": "custom_claim1",
"Value": "value1"
},
{
"Type": "custom_claim2",
"Value": "value2"
}
]
}
]
}
And, I load the clients config like the docs says:
var builder = services.AddIdentityServer(opts =>
{
/// Opts removed for simplicity
})
.AddInMemoryClients(Configuration.GetSection("IdentityServer:Clients"));
All is working fine, except for the client claims. I can not see them in Jwt.io decode tool.
There is a problem binding the Claims collection in the Clients[] from appSettings.json due to the fact that the current implementation of the Claim object can not be deserialized from json.
https://github.com/IdentityServer/IdentityServer4/issues/2573
and here
https://github.com/IdentityServer/IdentityServer4/pull/3887/files/ed14abc204960b2d5ca3418a868882a698e54d90

OpenIDM - How to assign default groups when creating a user

I want to assign default roles (using AD groups) at the user creation in OpenIDM.
I already created and setted role assignments using the OpenIDM REST API but I want to add the user to AD groups without adding him a specific role.
Here is on of my role mapping :
{
"properties": {
"description": "Data Provider Role"
},
"assignments": {
"ad": {
"attributes": [
{
"name": "ldapGroups",
"assignmentOperation": "mergeWithTarget",
"unassignmentOperation": "removeFromTarget",
"value": [
"CN=GRP_SHARE_USERS,CN=Users,DC=acme,DC=com"
]
}
]
},
"alfresco": {
"attributes": [
{
"name": "groups",
"value": "ACMEUSER"
}
],
"onAssignment": {
"file": "roles/onAssignment_alfresco.groovy",
"type": "groovy"
},
"onUnassignment": {
"file": "roles/onUnassignment_alfresco.groovy",
"type": "groovy"
}
}
}
}
How to proceed ? Can I modify the ldapGroups property on the "onCreate" script ?

Loopback unauthorized request when trying to login

I have created a custom user model that inherits form the built-in User model. Then I added a boot script that creates a default user.
User.create([{
username: 'admin',
email: 'email#email.com',
password: 'admin'
}], on_usersCreated);
This creates a document in the monogdb database,. However when I try to login, it send a 401 unauthorized request.
I tried to find the root of the problem, and it seems that loopback cannot fins such user in the database. More specifically, in node_modules/loopback/common/models/user there is this:
self.findOne({where: query}, function(err, user) {
var defaultError = new Error('login failed');
defaultError.statusCode = 401;
....
where
query = { email: "email#email.com" }
But the problem is that it cannot find a user with that email in the database although it does exist.
This is the user.json file
{
"name": "user",
"base": "User",
"idInjection": true,
"properties": {},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
}
],
"methods": []
}
I managed to solve this issue. As #superkhau stressed out, you should use the custom User model to create the default users.
Additionally, I had to expose the user model API and hide the build-in User API; So in model-config.js
...
"User": {
"dataSource": "db",
"public": false
},
"user": {
"dataSource": "db",
"public": true
},
...
You might have two collections: User and user. In your models, user extends from User. Which model did you use the create the user record?