IdentityServer4 fails to authenticate in memory users - identityserver4

We have an existing ID server configuration and I am trying to work on the UI without having to set up the rest of the connections. I followed the example to set up an MVC client:
In Startup.cs:
Console.WriteLine("Adding Test Users!!!");
serviceBulder.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers());
New client added:
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
}
}
Users are configured as the following:
public static List<TestUser> GetUsers()
{
return new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "alice",
Password = "password"
},
new TestUser
{
SubjectId = "2",
Username = "bob",
Password = "password"
},
};
}
MVC is configured to use the services:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services
.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
});
}
I have a couple of issues:
When I use AllowedGrantTypes = GrantTypes.Implicit per tutorial I get "Sorry, there was an error : unauthorized_client"
When I use the AllowedGrantTypes = GrantTypes.Hybrid I do get the login screen but my in memory users (alice and bob with pw = password) can't log in.
Questions:
- Does AddTestUsers not work when there are additional data stores present already?
- Why does my grant type differ from the walkthrough?

I'd check on the AllowedGrantTypes at the identity server level. We have roughly the same setup and we use AllowedGrantTypes = GrantTypes.HybridAndClientCredentials This allows the MVC application to talk with identity server behind the scenes.
The test users should be present.

Related

Identity server 4 Obtain role from token in webApi app

I have correctly configured identity server 4 which authorizes a web api for method access. However, I cannot use the roles in the web api, the role is in the token but when it arrives on the web api it does not give me authorization to enter the api.
IDS4 Configuration
new Client
{
ClientId = "spaclient",
ClientName = "SPA Client",
RequireConsent = false,
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
RequirePkce = true,
RequireClientSecret = false,
AllowAccessTokensViaBrowser = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"role"
}
}
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("spaclient", "SPA")
};
public static IEnumerable<ApiResource> ApiResources =>
new ApiResource[]
{
new ApiResource("spaclient", "SPA")
};
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource("role","User Role", new List<string>() { "role" })
};
CLIENT CONFIG
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:9002"; // --> IdentityServer Project
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
NameClaimType = "role",
RoleClaimType = "role"
};
});
CONTROLLER PART
[HttpGet]
[Authorize(Roles ="Administrator")] // <-- with role not work
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[HttpGet]
[Authorize]<-- without role work fine
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
In your access token, there is no role claim. You need to configure your existing ApiScope or ApiResource to include the necessar role claim.
What you have done is to only include it in your ID-token.
see my answer here about the relationship between the various resource types in IdentityServer
To add a userclaim to your APIScope, like this:
new ApiScope(name: "spaclient",
displayName:"SPA",
userClaims: new List<string>{ "role" }),
Also, you must request the spaclient and openid scopes as well.
To control the token lifetimes:
var client2 = new Client
{
ClientId = "authcodeflowclient",
IdentityTokenLifetime = 300, //5 minutes
AccessTokenLifetime = 3600, //1 hour
AuthorizationCodeLifetime = 300, //5 minutes
AbsoluteRefreshTokenLifetime = 2592000, //30 days
SlidingRefreshTokenLifetime = 1296000, //15 days
...
To complement this answer, I write a blog post that goes into more detail about this topic:
IdentityServer – IdentityResource vs. ApiResource vs. ApiScope

Identity Server 4 and User.Claims.Properties.Count always is 0 on client

I need to fill in the "Properties" in the client's claim.
I am writing down a claimon the IS4 server in the ProfileService class:
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// ...
Claim claim = new Claim("userData", "personalRights");
string valuePersonalRights = JsonConvert.SerializeObject(userRights);
claim.Properties.Add(GetKeyValuePair("rights", valuePersonalRights));
claims.Add(claim);
context.IssuedClaims.AddRange(claims);
}
private KeyValuePair<string, string> GetKeyValuePair(string key, string value)
{
KeyValuePair<string, string> keyValuePair = new KeyValuePair<string, string>(key, value);
return keyValuePair;
}
In this claim on the server there are records "Properties":
https://postgres-russia.ru/wp-content/files/is4_img/on_server.jpg
However, on the client, the properties of this claim are missing:
https://postgres-russia.ru/wp-content/files/is4_img/on_client.jpg
Client Configuration:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("domainGroups");
options.Scope.Add("geolocation");
options.Scope.Add("fullname");
options.SaveTokens = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
};
});
How to get claims properties on the client?
When you are defining your client, you can assign it's claims too, which will be included in access token.
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "spa",
ClientName = "SPA Client",
ClientUri = "",
AllowedGrantTypes = {GrantType.ResourceOwnerPassword,GrantType.ClientCredentials},
RedirectUris =
{
},
RequireClientSecret = false,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
PostLogoutRedirectUris = { "" },
AllowedCorsOrigins = { "","" },
AllowedScopes = { "openid", "profile","roles", IdentityServerConstants.LocalApi.ScopeName },
Claims = new Claim[]//look at this property
{
new Claim("prop1","value1")
}
}
};
This is a common problem when using Net Core. For some reason, the Firefox browser was simply silent, without showing errors, and the Chrome browser pointed to 431 Request Header Fields Too Large. The size of the statements in the cookie was over 4096 byte. Solved by using the ITicketStore and storing the claims as claims in the user session. More details: stackoverrun.com/ru/q/11186809
More: IIS Deployed ASP.NET Core application giving intermittent 431 Request headers too long error

Wobbly connection between javascript SignalR client and .net core backend

My JavaScript SignalR client routinely connects to my asp.net core implementation of IdentityServer4.
On the client I see:
Information: WebSocket connected to wss://localhost:5001/signal/satellite?
idsrv.session=4ec93d2f4c3b9a970ff82a537ae04d97&id=0eUBg2-xobp4CNttuIQJcg
But, it also doesn't connect much of the time, with an error that there was an unexpected token in JSON at position 0.
It seems like it runs well for a bit and then poorly, even if I re-login, it still throws the error. In the browser, the call always looks more or less like:
https://localhost:5001/signal/satellite?
idsrv.session=89364018a975e4fefff9ad0869b1ae09
I don't seem to need the middleware for it to move the querystring value into the header, I still get the user in my hub during onConnect with their sub claim, etc. However, if I do put in the middleware before app.UseAuthentication() and watch for:
context.Request.QueryString.Value
when it successfully handshakes with the server, I see:
idsrv.session=89364018a975e4fefff9ad0869b1ae09
but when it doesn't, I see this:
?client_id=internal&redirect_uri=https%3A%2F%2Flocalhost%3A5001%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile%20offline_access%20Harvey&response_mode=form_post&nonce=636817245670169226.ZWM2OWQ0ZWEtOTQzMC00YTJlLWI4MzQtYmIxZDZjOWVlYjg5ZTA4NTU2M2QtNjczZi00MTlmLThjYmQtZWUzZTQ1ODMzNDQ0&state=CfDJ8MCxOjJiJLdKqZrnPwOHDhqMnzWz6MqRb03SxToClqQ1F3n9g8yLdW683HRpZSHd-5wkN-6je4tHJkA8sc5i6YoKRxtMHwnWqxVW5-nXFaaH0TfOLUeqfxDzXLxnftmWFXLjK3Y7b6R2WzcDLEjChU1_Fk6X64SAHNRqeizGDPzRhxpV0U5w19Bbt7pUyRbYymn2WNedCS1F7g_wtwtJXDjCzWKBxqvPZ5Dtg99gxKkANalKYs7C4-fm7YdD0gFvsuV4CXsu0T06MjzID_zpA_F7TmSue4vGI-0_qY55Swc5mbLWUwKHtj6ZTfOG4UmTEP_hbj2PO9w2oNg9TWqTPtDC3-qSl1fTUkY0EtCwbA7F&x-client-SKU=ID_NETSTANDARD1_4&x-client-ver=5.2.0.0
so I'm stuck either not needing to do it because it succeeds on its own or the querystring never makes it to the middleware to move over (which is probably why it can't do it on its own)
What am I missing for it to consistently work?
Add Hybrid Client to IdentityServer4:
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true
}
and then in startup on the mvc client:
ServiceCollection.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "https://localhost:5001";
options.RequireHttpsMetadata = false;
options.ClientSecret = "secret";
options.ClientId = "mvc";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("offline_access");
options.Scope.Add("api1");
options.ClaimActions.MapJsonKey("website", "website");
});
This is modeled after the example: Quickstart5_HybridAndApi
In SignalR on server:
[Authorize]
public class SignalRSignalHub : Hub
{
public override Task OnConnectedAsync()
{
var context = Context.GetHttpContext();
return base.OnConnectedAsync();
}
}
SignalR JavaScript Client:
self.signalRConnection = new signalR.HubConnectionBuilder()
.withUrl("/signal/satellite?" + document.cookie).build();

IdentityServer4 with Windows authentication and custom claims

I have a problem with windows auth and custom claims.
I have an identityServer with windows auth and User.Identity.Name show my AD name.
But I cannot understand, how should I add some properties from my storage to this user. I have now something like this:
var claims = new List<Claim> {
new Claim("devhomepage", "www.devsite.com", ClaimValueTypes.String)};
var userIdentity = new ClaimsIdentity(claims, "siteinfo");
User.AddIdentity(userIdentity);
await HttpContext.SignInAsync(User);
return RedirectToLocal(returnUrl);
and it doesn't work :-) my client will be not authorized.
here is a config for the server
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = {"http://localhost:60640/signin-oidc"},// where to redirect to after login
PostLogoutRedirectUris = {"http://localhost:60640/signout-callback-oidc"},// where to redirect to after logout
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
AllowOfflineAccess = true,
RequireConsent = false
and is't a client
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:49245/";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
For SignIn I must use Microsoft.AspNetCore.Identity.SignInManager class with method SignInAsync(/*I should take hire a TUser object, that I stored in my database, and make mapping with my AD account */)
To use custom claims (will be used for all samples with custom claims in IS4):
public class ProfileService : IProfileService
{
protected UserManager<IdentityUser> _userManager;
public ProfileService(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//>Processing
var user = await _userManager.GetUserAsync(context.Subject);
//my custom claims
var claims = new List<Claim>
{
new Claim("devhomepage", "www"),
new Claim("reporting","reps")
};
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
//>Processing
var user = await _userManager.GetUserAsync(context.Subject);
context.IsActive = (user != null);
}
}
But this service I must register only after my IdentityService
services.AddIdentityServer()
...
services.AddTransient<IProfileService, ProfileService>();
}
of ConfigureServices

Access to API resource with Implicit flow

Does identity server 4 doesn't allow implicit flow to access API Resource.
Identity Server 4 config.cs
new Client
{
ClientId = "implicit",
ClientName = "Implicit Client",
AllowAccessTokensViaBrowser = true,
RedirectUris = { "https://notused" },
PostLogoutRedirectUris = { "https://notused" },
FrontChannelLogoutUri = "http://localhost:5000/signout-idsrv", // for testing identityserver on localhost
AccessTokenLifetime = 10,
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes = { "openid", "profile", "email", "ProxyServer", "api" }
}
Api Resouce
new ApiResource("api", "Custom"),
new ApiResource("ProxyServer", "Proxy Server")
In Mvc Client I am using this code ConfigureServices
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.Cookie.Name = "mvcimplicit";
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = Constants.Authority;
options.RequireHttpsMetadata = false;
options.ClientId = "implicit";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("ProxyServer");
options.Scope.Add("profile");
options.Scope.Add("email");
options.SaveTokens = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
};
});
When I try in browser I get "Sorry, there was an error : invalid_scope ". But if I remove options.Scope.Add("ProxyServer"); it works fine and Identity server 4 take me to login page.
OK I found the issue but posting just in case someone else face the same problem.
Response type needs to be specified explicitly otherwise it wont work.
options.ResponseType = "id_token token";
Modify the response type accordingly.

Resources