I am trying to create a custom user, authenticate them and then return the user claims into an angular application using identity server 3. I've looked over the samples, specifically the CustomUserService project.
Edit I've updated question based on progress to:
Within the starup.cs file I have the scopes, and clients loading
var idServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get());
//.UseInMemoryUsers(Users.Get());
Then the override in UserServices.cs
public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
string hashedPassword = string.Empty;
string salt = string.Empty;
//pull users
using (IdentityModelContext ctx = new IdentityModelContext())
{
IIdSrvUserRepository ur = new IdSrvUserRepository(ctx);
Users = ur.GetAll().ToList();
}
//salt curent password
var user = Users.SingleOrDefault(x => x.UserName == context.UserName);
if (user != null)
{
salt = user.Salt;
hashedPassword = IqUtils.GenerateSaltedPassword(context.Password, salt);
if (user.UserName == context.UserName && hashedPassword == user.Password)
{
try
{
context.AuthenticateResult = new AuthenticateResult(user.Subject, user.UserName);
}
catch (Exception ex)
{
string msg = $"<-- Login Error: Message: {ex.Message}, Inner Exception:{ex.InnerException} --->";
myLogger log = new myLogger("IdentityServer");
}
}
}
return Task.FromResult(0);
}
Where I am having trouble is on the GetProfileDataAsync method. I cannot figure out how to add claims to the client or if I need to invoke a second client.
The application is an angular client authenticating to Idsrv. I have one client created
//Angular client authentication
new Client
{
ClientId = "iqimplicit",
ClientName = "IQ Application (Implicit)",
Flow = Flows.Implicit,
AllowAccessToAllScopes = true,
IdentityTokenLifetime = 300,//default value is 5 minutes this is the token that allows a user to login should only be used once
AccessTokenLifetime = 3600, // default is one hour access token is used for securing routes and access to api in IQ
RequireConsent = false,
RequireSignOutPrompt = true,
AllowedScopes = new List<string>
{
//no clue if this is correct
StandardScopes.Profile.Name
},
RedirectUris = new List<string>
{
angularClient + "callback.html"
},
PostLogoutRedirectUris = new List<string>()
{
//redirect to login screen
angularClient
}
}
Following the SO posting here I was trying to add the claims to the access token returned to the client.
The scope I have defined is just for the Resource. I assume I need to create a second for identity similar to the commented out section?
return new List<Scope>
{
StandardScopes.OpenId,
StandardScopes.ProfileAlwaysInclude,
StandardScopes.Address,
new Scope
{
Name = "appmanagement",
DisplayName = "App Management",
Description = "Allow application to management.",
Type = ScopeType.Resource,
Claims = new List<ScopeClaim>()
{
new ScopeClaim("role", false),
new ScopeClaim("Name", true),
new ScopeClaim("GivenName", true),
new ScopeClaim("FamilyName", true),
new ScopeClaim("Email", true),
},
},
// new Scope
// {
// Name = "iquser",
// DisplayName = "User",
// Description = "Identifies the user",
// Type = ScopeType.Identity,
// Claims = new List<ScopeClaim>()
// {
// new ScopeClaim("Name", true)
// }
// }
};
}
}
}
The information I want to pass back as claims are indicated above, (name, givenname, familyname email).
Attempting to follow the comments from the previously mentioned SO postI overrode the GetProfileDataAsync
which should be getting the information for the user.
public override Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// issue the claims for the user
var user = Users.SingleOrDefault(x => x.Subject == context.Subject.GetSubjectId());
if (user != null)
{
if(context.RequestedClaimTypes != null)
try
{
if (context.RequestedClaimTypes != null)
{
List<Claim> newclaims = new List<Claim>();
foreach (Claim claim in context.Subject.Claims)
{
if (context.RequestedClaimTypes.Contains(claim.Type))
{
newclaims.Add(claim);
}
}
context.IssuedClaims = newclaims;
}
}
catch (Exception ex)
{
string msg = $"<-- Error Getting Profile: Message: {ex.Message}, Inner Exception:{ex.InnerException} --->";
myLogger log = new myLogger("IdentityServer");
}
}
//return Task.FromResult(context.IssuedClaims);
return Task.FromResult(0);
}
}
And this is where I am stuck While I see the claims I defined in the context.RequestedClaimTypes
There is never a match in claim.Type
as it always contains the base properties of the idsrv claim.
Since this is a custom database all information that is going on the claims is stored in the Users table of the database and not within a Claims table. I was trying to map from the user table the values of each field into the Claim value.
Update I've gone back and uncomment from the scope and added the following claims to an identity scopeType
new Scope
{
Name = "iuser",
DisplayName = "User",
Description = "Identifies the user",
Type = ScopeType.Identity,
Claims = new List<ScopeClaim>()
{
new ScopeClaim(Constants.ClaimTypes.Name, alwaysInclude: true),
new ScopeClaim(Constants.ClaimTypes.GivenName, alwaysInclude: true),
new ScopeClaim(Constants.ClaimTypes.FamilyName, alwaysInclude: true),
new ScopeClaim(Constants.ClaimTypes.Email, alwaysInclude: true),
}
}
Initial Authentication works and on the callback I can see the scopes evaluated. However I'm still stuck on how I would now get the values from the DB applied to these scopes. This section of code is (as expected) looking for the System.Security.Claims.Claim
type. This is now the stopping point in determining how to get the claim values which are properties on my user applied and returned as claims.
At this point I am redirected back to the angular app but the accesstoken object is blank for user information.
How do I go about inserting my own values into the context.IssuedClaims
as claims at this point?
Thanks in advance
IdentityServer emits claims about users and clients into tokens. You are in full control of which claims you want to emit, in which situations you want to emit those claims, and where to retrieve those claims from.
Simply put: Claims are assertions that one subject (e.g. a user or an Authorization Server) makes about itself or another subject. Scopes are groups of claims.
A couple of comments on what you seem to be trying to do.
profile
scope.You don't need to define your own scope iuser
, if all the claims you want are in a OIDC standard scopes (here: profile
/ email
). The claims you're declaring present in your iuser
identity scope is already covered by the profile
scope in the standard. So just ask for the profile
scope instead, and make your client be allowed to request that scope.
Standard scopes, and what their claim conents are: http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
AllowedScopes
configuration on your client configuration is a bit wrong.Use the following constants instead.
AllowedScopes = new List<string>
{
Constants.StandardScopes.OpenId,
Constants.StandardScopes.Profile,
Constants.StandardScopes.Email,
}
See also this sample config. https://github.com/IdentityServer/IdentityServer3.Samples/blob/master/source/_sharedConfiguration/Clients.cs
.. but remember to supply your request towards idsrv that you want a id_token back including claims from the profile
scope. The request towards /authorize
, /userinfo
or /token
endpoint must in other words inlcude a scope
param with a value of at least openid profile
.
For example, like Brock A. does in his oidc js client library: https://github.com/IdentityServer/IdentityServer3.Samples/blob/master/source/Clients/JavaScriptImplicitClient/app.js#L18
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With