TLDR; In the context of using IdentityServer4
How do you get email address and hd claims from Google?
How do you get User.Identity.Name to be populated?
I have worked through the IdentityServer quickstarts and have a working MVC client talking to a IdentityServer instance (apologies if using the wrong terminology). I am using External Authentication (Google) and do not have anything mildly complicated such as local logins / database etc. I am not using ASP.NET Identity. This is all working just fine.
I can successfully authenticate in my MVC app and the following code produces the claims in the screenshot below:
#foreach (var claim in User.Claims)
{
<dt>#claim.Type</dt>
<dd>#claim.Value</dd>
}
<dt>Identity.Name</dt>
<dd> #User.Identity.Name</dd>
<dt>IsAuthenticated</dt>
<dd>#User.Identity.IsAuthenticated</dd>
Questions:
I cannot retrieve extra claims (right term?) from Google. Specifically 'hd' or even 'email' - note that they don't show up in the claims in the above screenshot. How do I get the email address and hd claims from Google? What am I missing or doing wrong?
Note that the output of User.Identity.Name is empty. Why is this and how do I get this populated? This seems to be the only property of User.Identity that isn't set.
My setup is as follows - you can see the output of this as above:
Client (MVC)
In Startup.cs, ConfigureServices
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = Configuration["App:Urls:IdentityServer"];
options.RequireHttpsMetadata = false;
options.Resource = "openid profile email";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("domain");
options.ClientId = "ctda-web";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
});
Identity Server
Client definition
// OpenID Connect implicit flow client (MVC)
new Client
{
ClientId = "ctda-web",
ClientName = "Company To Do Web App",
AllowedGrantTypes = GrantTypes.Implicit,
EnableLocalLogin = false,
// where to redirect to after login
RedirectUris = { "http://localhost:53996/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:53996/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"domain"
}
}
IdentityResource definition
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource
{
Name = "domain",
DisplayName = "Google Organisation",
Description = "The hosted G Suite domain of the user, if part of one",
UserClaims = new List<string> { "hd"}
}
};
The answer is amazingly unobvious: the sample code provided by IdentityServer4 works as long as you have the following configuration (in the Identity Server Startup.cs):
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients()
.AddTestUsers(Config.GetUsers()); //<--- this line here
Why? Because AddTestUsers is doing a bunch of the plumbing you need to do in your own world. The walkthrough implicitly assume you move to EF or ASP.NET Identity etc and make it unclear what you have to do if you aren't going to use these data stores. In short, you need to:
Create an object to represent the user (here's a starter)
Create a persistance/query class(here's a starter)
Create an instance of IProfileService which ties this all together, putting in your definitions of a User and a UserStore (here's a starter)
Add appropriate bindings etc
My IdentityServer Startup.cs ended up looking like this (I want to do in memory deliberately, but obviously not use the test users provided in the samples):
services.AddSingleton(new InMemoryUserStore()); //<-- new
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients())
.AddProfileService<UserProfileService>(); //<-- new
Turns out Google does return email as part of the claim. The scopes in the code sample of the question worked.
I can tell you how to get email to be returned.
There are two ways to do this but they both require that you add the email scope to the initial request. Just sending openId isnt going to work.
Openid email
UserInfo request
Now when you get the access token back you can do
https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token={access token}
Response
{
"family_name": "Lawton",
"name": "Linda Lawton",
"picture": "https://lh5.googleusercontent.com/-a1CWlFnA5xE/AAAAAAAAAAI/AAAAAAAAl1I/UcwPajZOuN4/photo.jpg",
"gender": "female",
"email": "xxxx#gmail.com",
"link": "https://plus.google.com/+LindaLawton",
"given_name": "Linda",
"id": "117200475532672775346",
"verified_email": true
}
Token Info Request:
Using the id token
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={token id}
response
{
"azp": "07408718192.apps.googleusercontent.com",
"aud": "07408718192.apps.googleusercontent.com",
"sub": "00475532672775346",
"email": "XX#gmail.com",
"email_verified": "true",
"at_hash": "8ON2HwraMXbPpP0Nwle8Kw",
"iss": "https://accounts.google.com",
"iat": "1509967160",
"exp": "1509970760",
"alg": "RS256",
"kid": "d4ed62ee21d157e8a237b7db3cbd8f7aafab2e"
}
As to how to populate your controller i cant help with that.
Related
User can login to identity server using local account. But this is cause of user sign out from MVC client that uses open id connect for external login and I don't know why exactly!
I checked IdentityServer4 connect/authorize endpoint for any sign out code but I can't find anything.
IdentityServer config:
new Client
{
ClientId = "mvc",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
AllowOfflineAccess = true,
RequireConsent = true,
// where to redirect to after login
RedirectUris = { "https://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
}
}
Client config:
builder.Services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddAuthentication()
.AddOpenIdConnect("oidc", "Demo IdentityServer4", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("profile");
options.SaveTokens = true;
});
Aren't you by any chance using the same name for the cookies of your MVC client and your identity server?
As you are working in localhost for both MVC client and identity server, the cookie of single sign on could be overwriting the MVC cookie.
Have you checked this behavior with different domains?
And, what if you go back to the MVC Client and login from there, do you lose the single sign on? If I'm right, this time the MVC Cookie would be overwriting the identity server Cookie.
Did you check #IdentiyServer4 logs? I think those errors would help you to find the exact problem.
I have a application which is like Microsoft default templates of Asp.netCoreWebApplication->ASP.netCoreWithReact.js.
In this react client will be wrapped inside a.netcore project. All of the UI pages will be served from React. .Net core backend will be used only for APIs.
Now I have implemented IdentityServer4 and able to generate token at this end point,
http://localhost:60739/token
From react client, on login button click I could make API call to http://localhost:60739/token and could generate token using granttype password flow. but i could not validate authroize my api end points with that token
below is my client definition with in identity server solution,
new Client
{
ClientName = "Resource Owner Flow",
ClientId = "resource_owner_flow",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("resource_owner_flow_secret".Sha256())
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.OfflineAccess
},
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse,
//AccessTokenLifetime = 60,
RefreshTokenExpiration = TokenExpiration.Absolute,
AbsoluteRefreshTokenLifetime = 300
}
below is piece of code i am using to refer identity server,
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:60739";
options.TokenValidationParameters.ValidateAudience = false;
options.TokenValidationParameters.ValidateIssuer = false;
options.TokenValidationParameters.ValidateIssuerSigningKey = false;
options.TokenValidationParameters.ValidateLifetime = false;
options.TokenValidationParameters.ValidateTokenReplay = false;
options.IncludeErrorDetails = true;
});
I could not successfully access protected api resources? Any idea ?
If i Call http://localhost:57102/api/Home/GetSomeProtectedData using token in header i get 500 internal server error. If i remove authorize attribute on protected resource it's working fine.
below is postman screenshot,
for token creation below is the log,
IdentityServer4.Validation.TokenRequestValidator: Information: Token request validation success,
{
"ClientId": "resource_owner",
"ClientName": "Resource Owner",
"GrantType": "password",
"Scopes": "openid",
"AuthorizationCode": "********",
"RefreshToken": "********",
"UserName": "superadmin#gmail.com",
"Raw": {
"username": "superadmin#gmail.com",
"password": "***REDACTED***",
"grant_type": "password",
"scope": "openid",
"response_type": "token"
}
}
but when I request protected API resource with token in header i get 500 internal server error and logs got created.
If you are getting 500 error, the problem is not the authorization flow. Else it would be 401.
You need to check out what is the real error. It can be a parsing error. You simpyly cannot finish the process because of an server side error. Please share more info, may be logs so I update the answer.
I have a project running on localhost:44387 which is the IdentityServer configuration.
I have an ASP.NET Core application running on localhost:44373 which acts as a front end application for the user to engage with and another ASP.NET Core application running on localhost:44353 which acts as an API.
When the user tries to access an authorized controller in the front end application, they are redirected to the login page on the IdentityServer.
Once the user has logged in, they are redirected back.
They are then authorized on the front end application, but when calls are being made to the API on localhost:44353, it returns unauthorized.
I have tried to add a scope to the .OpenIdConnect method to add the API as a scope but it crashes the application when redirecting to the login page.
How can I add the API as a permission to request, so once the front end application is authorized it can call the API?
This is in the Config.cs file for the IdentityServer
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.Implicit,
// where to redirect to after login
RedirectUris = { "https://localhost:44373/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:44373/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"roles",
"staff_api" // <---- Add staff api as scope
},
RequireConsent = false,
}
Inside the Startup of the front end app
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", options =>
{
options.Authority = baseAuthAddress;
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
//options.Scope.Add("staff_api"); <--- THIS MAKES IT CRASH?
options.Scope.Add("roles");
// Fix for getting roles claims correctly :
options.ClaimActions.MapJsonKey("role", "role", "role");
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "roles";
});
Inside Startup.cs of API
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Audience = "staff_api"; ;
options.Authority = Configuration["AuthURL"];
});
Have you added and seeded an ApiResource and ApiScope on the IdentityServer side? (With the newer versions of IdentityServer)
Like shown in the quickstarts? Since we don't see the full Config.cs file, that would be the first thing to check.
You should also have a look at the .well-known/openid-configuration of your IS4, to see if the scope for the api is registered in the section scopes_supported (see link to quickstart as well).
The Debug output of IdentityServer, TokenValidationMiddleware on the API side and the AuthenticationMiddleware on the client side are very verbose, you should check the debug output for entries that inform you what is not working.
Also you should not use GrantTypes.Implicitfor Asp.Net Core applications if it is not
a SPA, this type is intended for JS-based front-ends.
Hi I have inherit a system like this:
An Api and many Fronts (spas) they share a common menu with links to navigate to each others but they are different react apps, with different urls. And Azure Active directory to authenticate an the Api is protected with Bearer token.
Something like this:
Now I have authorization requirements with a custom permissions that the business people want to assign to every user, for actions that they can do or not and visibility things.
I want to use Identity4Server with the active directory as an open id provider. Then consume a provider api to get custom permission and put those permissions in the claims. Then in the Api impl policies that demand for specify roles and claims to accomplish the permissions specifications.
Something like this:
Identity4Server config:
services.AddAuthentication()
.AddOpenIdConnect("oidc", "OpenID Connect", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SaveTokens = true;
options.RequireHttpsMetadata = false;
options.Authority = "https://login.microsoftonline.com/tenant/";
options.ClientId = "ClientId";
options.ClientSecret = "ClientSecret";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
Api:
services
.AddAuthentication(configure =>
{
configure.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
configure.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Audience = "api";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
});
var clientsPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Bearer")
.AddRequirements(new ClaimsAuthorizationRequirement("ClientsModule", new[] { "1" }))
.RequireRole("Admin")
.Build();
services.AddAuthorization(options =>
{
options.AddPolicy("Clients", clientsPolicy);
});
For the react apps I'm using this npm "oidc-client": "1.7.0" and a similar approach to https://medium.com/#franciscopa91/how-to-implement-oidc-authentication-with-react-context-api-and-react-router-205e13f2d49
And the Clients config is: (Provider its quite similar the only thing that change is url localhost:3001)
export const IDENTITY_CONFIG = {
authority: "http://localhost:5000",
clientId: "fronts",
redirect_uri: "http://localhost:3000/signin-oidc",
login: "http://localhost:5000/login",
automaticSilentRenew: false,
loadUserInfo: false,
silent_redirect_uri: "http://localhost:3000/silentrenew",
post_logout_redirect_uri: "http://localhost:3000/signout-callback-oidc",
audience: "fronts",
responseType: "id_token token",
grantType: "password",
scope: "openid api",
webAuthResponseType: "id_token token"
};
If the user login into clients (localhost:3000) front and then navigate to providers (localhost:3001) front it shouldn't login again. To accomplish this I configure all the fronts with the same client id, but I don't know if this is the correct way to do it. Now my config class in identity server is:
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "fronts",
ClientSecrets =
{
new Secret("secret".Sha256())
},
ClientName = "All fronts",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:3000/signin-oidc", "http://localhost:3001/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:3000/signout-callback-oidc", "http://localhost:3001/signout-callback-oidc" },
AllowedCorsOrigins = { "http://localhost:3000", "http://localhost:3001" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api"
}
}
};
}
Do you think this configuration is the correct way to do it or there is a better approach?
You mentioned
many different react apps, with different urls
but in your code snippet I see only the Clients(localhost:3000).
Anyway, the protocol spec tells us to register as many clients as we need. SSO is the main responsibility of identity provider.
You just need to add RequireConsent = false; to your client def in IdSrv to avoid additional unintended user interaction.
Additionally, nowadays the recommended auth flow for spa-s is "code+pkce". You can take a look at this article in order to get detailed info for transition.
I'm implementing an authentication server with IdentityServer4 for clients using Hybrid flow. I managed to implement my own user store and also my own repository for clients, grants and resources.
When a user wants to login the client redirects it to my authentication server and if it's not loged in, it shows the login page. At this point I need some extra information than username and password in order to login my users. This is a projectId from another system where I'm actually authentication the users to. The client should provide this projectId.
The flow looks like that:
flow
I've read here Sending Custom Parameters to login screen
that I should retrieve parameteres from the returnUrl I get in the AccountController. The way I'm triggering the login flow right now is with the [Authorize] attribute in a controller method in my client code:
[Route("login")]
[Authorize]
public async Task<IActionResult> Login()
My questions are:
1.How can I send the projectId in the connect/authorize request to identity server?
2.Should I create the request manually for that?
2.a If so,then how can I handle the redirect uri action in the controller? Because now i'm using the /signin-oidc standard route.
My client definition looks like that:
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:5001";
options.RequireHttpsMetadata = false;
options.ClientId = "BGServer";
options.ClientSecret = "ThisIsTheBGServerSecret";
options.ResponseType = "code id_token"; //"code";
//set SaveTokens to save tokens to the AuthenticationProperties
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("BG_API");
options.Scope.Add("offline_access");
});
}
And my client definition in the authentication server looks like that:
// OpenID Connect hybrid flow and client credentials client (BGServerClient)
new Client
{
ClientId = "BGServer",
ClientName = "BabyGiness Server",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = false,
ClientSecrets =
{
new Secret("ThisIsTheBGServerSecret".Sha256())
},
RedirectUris = {"http://localhost:5005/signin-oidc"},
PostLogoutRedirectUris = { "http://localhost:5005/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"BG_API"
},
AllowOfflineAccess = true //used to be able to retrieve refresh tokens
};
Thank you very much for your help.
You should be able to simply add additional query string parameters to the authorize endpoint request and then parse them out of the returnUrl in your MVC controller for the login flow. Anything not part of the protocol will be ignored by by IDS4 I'm pretty sure.