I am using Identity Server 4 with an Angular client.
In both sides I am adding the same scopes:
'openid profile offline_access email api'
However I get an error:
Sorry, there was an error : invalid_scope
Invalid scope
I checked the Well-Known OpenID configuration and I see:
"scopes_supported": [
"openid",
"profile",
"api",
"offline_access"
]
So the email scope does not show?
Might this be the problem? Why does this happen?
My Identity Server 4 Client definition is:
new Client {
ClientId = "spa",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
AllowAccessTokensViaBrowser = true,
AllowOfflineAccess = true,
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OfflineAccess,
"api"
},
RedirectUris = new List<String> {
"https://localhost:5001",
"https://localhost:5001/index.html",
"https://localhost:5001/silent-refresh.html"
},
PostLogoutRedirectUris = new List<String> {
"https://localhost:5001/"
},
AllowedCorsOrigins = new List<String> {
"https://localhost:5001"
}
}
You need to add an IdentityResource:
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email()
};
}
Then in startup.cs add this:
var identityServerBuilder = services.AddIdentityServer(...)
.AddInMemoryIdentityResources(GetIdentityResources())
Related
I am trying to get a Blazor WebAssembly project working with an existing ASP.NET Core app that uses IdentityServer4(3.1).
When the Blazor app calls the following URL
http://192.168.0.151:5002/connect/authorize?client_id=myWebClient&redirect_uri=http%3A%2F%2F192.168.0.151%3A44387%2Fauthentication%2Flogin-callback&response_type=code&scope=openid%20profile%20openid%20profile&state=5d01e8dfd9ea4c3dbc207839f147bd37&code_challenge=-cXZb1q16t4eTMBaT8-lbeFGRr7V9YIbkl072JPhpQ4&code_challenge_method=S256&response_mode=query
I see from the logs that there is a null reference in AddQueryString.
Not sure how to resolve this.
IdentityServer4 is configured as follows
ClientId = "myWebClient",
ClientName = "My Web Client",
AllowedGrantTypes = GrantTypes.Code,
RequireClientSecret = false,
RequirePkce = true,
AllowedScopes =
{
"openid",
"profile",
},
AllowedCorsOrigins = { "http://192.168.0.151:44387" },
RedirectUris = { "http://192.168.0.151:44387/authentication/login-callback" },
PostLogoutRedirectUris = { "http://192.168.0.151:44387/" },
Enabled = true
And Blazor as follows
"Authority": "http://192.168.0.151:5002/",
"ClientId": "myWebClient",
"PostLogoutRedirectUri": "http://192.168.0.151:44387/",
"RedirectUri": "http://192.168.0.151:44387/authentication/login-callback",
"ResponseType": "code",
"DefaultScopes": [ "openid", "profile" ]
With IdentityServer initialised as follows
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources())
.AddInMemoryApiResources(IdentityServerConfig.GetApiResources())
.AddInMemoryClients(IdentityServerConfig.GetClients())
.AddAspNetIdentity<User>().AddProfileService<CustomProfileService>();
Any help greatly appreciated.
I think what I am missing are server pages that handle
login/logout etc. I assumed incorrectly these were present in Blazor webassembly.
I have a MVC client accessing a web API protected by IDS4 server. It works perfectly when I use ResponseType = "code". But when I change it to ResponseType = "code id_token" (hybrid flow), I start to get this "Sorry, there was an error : invalid_request code challenge required" error.
Here is my client configuration on the IDS4 side:
new Client
{
ClientId = "mvc",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequirePkce = false,
//AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { "https://localhost:6009/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:6009/signout-callback-oidc" },
AllowOfflineAccess = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"roles",
"MyAPI"
}
And here is the configuration on the MVC Client side. Note that it is the 'options.ResponseType = "code id_token"' that made the error occur. It will not error if I use "code" instead.
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:6005";
options.ClientId = "mvc";
options.ClientSecret = "secret";
//options.ResponseType = "code";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.Scope.Add("profile");
options.Scope.Add("email");
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("roles");
options.ClaimActions.MapJsonKey("role", "role", "role");
options.TokenValidationParameters.RoleClaimType = "role";
options.Scope.Add("MyAPI");
options.Scope.Add("offline_access");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
};
});
And here is what's in the log:
[2021-03-12T15:25:51.014Z] [4] [Error] Request validation failed
[2021-03-12T15:25:51.013Z] [4] [Error] code_challenge is missing
{
"ClientId": "mvc",
"RedirectUri": "https://localhost:6009/signin-oidc",
"AllowedRedirectUris": [
"https://localhost:6009/signin-oidc"
],
"SubjectId": "anonymous",
"ResponseType": "code id_token",
"ResponseMode": "fragment",
"GrantType": "hybrid",
"RequestedScopes": "",
"State": "CfDJ....8oxh4",
"PromptMode": "",
"Raw": {
"client_id": "mvc",
"redirect_uri": "https://localhost:6009/signin-oidc",
"response_type": "code id_token",
"scope": "openid profile email roles MyAPIoffline_access",
"response_mode": "form_post",
"nonce": "637511...lOGNh",
"state": "CfDJ8...8oxh4",
"x-client-SKU": "ID_NETSTANDARD2_0",
"x-client-ver": "5.5.0.0"
}
}
By the way, I am testing hybrid flow because I am trying to make the Windows authentication working under IDS4. My local login works but not Windows. Some resource says that hybrid flow is needed for that. Can somebody tell me what I am doing wrong here?
It could be that IdentityServer always requires PKCE and that's why it complains when you don't provide the necessary challenge.
Dominic writes here:
Code flow without PKCE is vulnerable to code injection attacks. That's why we do not expose it.
Adding the code challenge/verifier in a client is not that complicated.
Here's how I present OAuth 2.1 in my training classes:
I'm trying to setup a solution with the following:
- IdentityServer4 instance
- React / js client
- ASP.NET Core API (protected by IdentityServer)
Since I want to use roles and claims, I would like to use a reference token (id_token) and have the API verify the claims against the IdentityServer.
Configuration for the IdentityServer instance:
"IdentityServer": {
"IdentityResources": [
"openid",
"email",
"phone",
"profile"
],
"ApiResources": [
{
"Name": "b2a6f5a1-9317-4b2f-bb02-c2f7cd70ce9a",
"DisplayName": "My API",
"ApiSecrets": [ { "Value": "<BASE 64 ENCODED SHA256 HASH OF SECRET>" } ]
}
],
"Clients": [
{
"Enabled": true,
"ClientId": "976d5079-f190-41a2-a6f6-be92470bacc0",
"ClientName": "My JS client",
"ClientUri": "http://localhost:3000",
"LogoUri": "logo.png",
"RequireClientSecret": false,
"AllowAccessTokensViaBrowser": true,
"RequireConsent": false,
"ClientClaimsPrefix": null,
"AccessTokenType": "reference",
"AllowedGrantTypes": [ "implicit" ],
"RedirectUris": [ "http://localhost:3000/authentication/login-callback" ],
"PostLogoutRedirectUris": [ "http://localhost:3000/authentication/logout-callback" ],
"AllowedCorsOrigins": [ "http://localhost:3000" ],
"AllowedScopes": [ "openid", "email", "phone", "profile", "b2a6f5a1-9317-4b2f-bb02-c2f7cd70ce9a" ]
}
]
}
Configuration for the (protected) API:
"Identity": {
"Authority": "https://localhost:44311",
"ApiName": "b2a6f5a1-9317-4b2f-bb02-c2f7cd70ce9a",
"ApiSecret": "<UNHASHED SECRET>"
}
Startup.cs for the API:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration.GetValue<string>("Identity:Authority");
options.ApiName = Configuration.GetValue<string>("Identity:ApiName");
options.ApiSecret = Configuration.GetValue<string>("Identity:ApiSecret");
});
I query IdentityServer for an id_token with the following parameters:
export const Settings: any = {
authority: "https://localhost:44311",
post_logout_redirect_uri: "http://localhost:3000/authentication/logout-callback",
redirect_uri: "http://localhost:3000/authentication/login-callback",
response_type: "id_token",
scope: "openid email profile phone b2a6f5a1-9317-4b2f-bb02-c2f7cd70ce9a"
};
I get the following error: Requests for id_token response type only must not include resource scopes.
If I change the scope to:
export const Settings: any = {
// ...
scope: "openid email profile phone" // removed (protected) api resource
};
it works and I get an id_token like this:
{
"nbf": 1573798909,
"exp": 1573799209,
"iss": "https://localhost:44311",
"aud": "976d5079-f190-41a2-a6f6-be92470bacc0",
"nonce": "d768a177af684324b30ba73116a0ae79",
"iat": 1573798909,
"s_hash": "HbWErYNKpgsiOIO82IiReA",
"sid": "vVWVhLnVLiCMdLSBWnVUQA",
"sub": "90f84a26-f756-4923-9d26-6104eef031ac",
"auth_time": 1573798909,
"idp": "local",
"preferred_username": "noreply",
"name": "noreply",
"email": "noreply#example.com",
"email_verified": false,
"amr": [
"pwd"
]
}
Note that the audience is 976d5079-f190-41a2-a6f6-be92470bacc0, which is the js client.
When I use this token on the protected API, it says:
Bearer error="invalid_token", error_description="The audience '976d5079-f190-41a2-a6f6-be92470bacc0' is invalid"
which is not that strange since the API has the id b2a6f5a1-9317-4b2f-bb02-c2f7cd70ce9a.
So my question is: Where am I wrong? How do I get the token for the correct audience?
The ID token will be validated by your client app (React/js) app to get user claims , so the audience is your client app's client ID . A token passe to your web api should be validated by web api , so the audience is web api's name .
The ID token contains information about an End-User which is not used to access protected resource , while Access token allows access to certain defined server resources .You can set response_type to id_token token , and add api name/scope to scope configuration . With implicit flow , after authentication , client will get one ID token and one Access token , you can now use access token to access the protected web api .
I get an invalid grant when I try to login from my WinForms app to IS4.
This is the server log:
fail: IdentityServer4.Validation.TokenRequestValidator[0]
Unexpected code_verifier: 12a783b32873a5b4ae0eb7113a067cd978d3d345a8cb29cc0a1a6df131c5839a
fail: IdentityServer4.Validation.TokenRequestValidator[0]
{
"ClientId": "las",
"ClientName": "LAS.NET Client",
"GrantType": "authorization_code",
"AuthorizationCode": "e301575cc20f47acf7c15178310f776642a7a30cf2b6a05f54702097b1645b7a",
"Raw": {
"grant_type": "authorization_code",
"code": "e301575cc20f47acf7c15178310f776642a7a30cf2b6a05f54702097b1645b7a",
"redirect_uri": "http://localhost/winforms.client",
"code_verifier": "12a783b32873a5b4ae0eb7113a067cd978d3d345a8cb29cc0a1a6df131c5839a",
"client_id": "las",
"client_secret": "secret"
}
}
The LoginResult.Error says "invalid_grant".
This is the client setup:
new Client
{
ClientId = "las",
ClientName = "LAS.NET Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
"api1"
},
RedirectUris = { "http://localhost/winforms.client" },
AllowOfflineAccess = true,
RequireConsent = false
},
and this is how I initialize my winform app:
var options = new OidcClientOptions
{
Authority = "http://localhost:5000",
ClientId = "las",
ClientSecret = "secret",
RedirectUri = "http://localhost/winforms.client",
Scope = "openid profile api1 offline_access",
Browser = new WinFormsEmbeddedBrowser(),
Flow = OidcClientOptions.AuthenticationFlow.Hybrid
};
_oidcClient = new OidcClient(options);
How can I fix this issue?
Your WinForms client is telling IdentityServer that it wants to do PKCE however the client does not look like it requires PKCE. In your client configuration/setup add RequirePkce = true.
I'm currently stuck on getting all the users with certain role, for example admin users, in one angular SDK controller.
according to the docs of strongloop. what I did was:
User.find({
filter: {
include: [{'relation':'roles', 'scope': {
where:{
name:'admin',
}}
}],
},
}, function(list) {
console.log(list);
});
But the list i got is all the users, the non-admin users are included too. On the server side it is the default codes, i didn't change them.
{
"name": "user",
"plural": "Users",
"base": "User",
"properties": {
},
"relations": {
"roles": {
"type": "belongsTo",
"model": "RoleMapping",
"foreignKey": "principalId"
}
},
"acls": [],
"methods": []
}
Could you tell me what I made wrong? I don't want to loop through all the "list" from that query and filter the admin users, because it is a very huge list of users, but admin is for only 2 or 3 persons.
Here is the solution of what i did, from the common/models/user.js, i created a remotemethod, called "getUsersByRole", and only accept "role", which is the name of the role:
User.remoteMethod('getUsersByRole', {
accepts: [
{ arg: 'role', type: 'string', required: true },
],
returns: {arg: 'users', type: 'string'},
http: {
verb: 'get',
path: '/byrole/:role'
}
});
then here is the function of it:
User.getUsersByRole = function(role, cb) {
var loopback = require('loopback');
var Role = loopback.getModel('Role');
var userIdList = [];
Role.findOne({include:'principals', where: {name:role}}, function(err, role) {
role.principals(function(err, principals) {
for (var i = 0; i < principals.length; i++) {
userIdList.push(parseInt(principals[i].principalId));
}
if (userIdList.length > 0) {
User.find({where: {id: {inq: userIdList}}}, function(err, users) {
cb(err, users);
});
} else {
cb(err, false);
}
});
});
}
then run the lb-ng command to generate the service for angular client side, then run:
User.getUsersByRole({role:rolename}, function(list) {
});
in the controller.
Can you run the query from the role instead?
Role.find({
filter: {
where: {name:'admin'},
include: {'relation':'users'}
},
}, function(list) {
console.log(list);
});