I want to know how I can return my generated JWT token to user. I mean I would like this token to be saved e.g. somewhere in the user's browser and sent to my Controller in every query after successfully login.
The token generating functions are already written. Everything works, unfortunately, I do not know how to pass it to the user and then pick it up in other functions / controllers
This is the login controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(LoginDto dto)
{
if (ModelState.IsValid)
{
string token = _accountService.GenerateJwt(dto); //working
if (token != null)
{
// how can I return token there?
return RedirectToAction("LoginSuccess");
}
else
{
ViewBag.error = "Login failed";
return RedirectToAction("LoginFailed");
}
}
}
This is the function that generates the token:
public string GenerateJwt(LoginDto dto)
{
var user = _context.dnozasg2lp_vip_users.FirstOrDefault(u => u.Email == dto.Email);
if (user is null)
{
throw new BadRequestException("Invalid username or password!");
}
var result = _passwordHasher.VerifyHashedPassword(user, user.Password, dto.Password);
if (result == PasswordVerificationResult.Failed)
{
throw new BadRequestException("Invalid username or password!");
}
var claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, user.ID.ToString()),
new Claim(ClaimTypes.Name, $"{user.FirstName} {user.LastName}"),
new Claim(ClaimTypes.Email, $"{user.Email}")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_authenticationSettings.JwtKey));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expiration = DateTime.Now.AddDays(_authenticationSettings.JwtExpireDays);
var token = new JwtSecurityToken(_authenticationSettings.JwtIssuer,
_authenticationSettings.JwtIssuer,
claims,
expires: expiration,
signingCredentials: credentials
);
var tokenHandler = new JwtSecurityTokenHandler();
return tokenHandler.WriteToken(token);
}
And my startup config file:
// JWT Token config below
var authenticationSettings = new AuthenticationSettings();
services.AddSingleton(authenticationSettings);
Configuration.GetSection("Authentication").Bind(authenticationSettings);
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = "Bearer";
option.DefaultScheme = "Bearer";
option.DefaultChallengeScheme = "Bearer";
}).AddJwtBearer(cfg =>{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidIssuer = authenticationSettings.JwtIssuer,
ValidAudience = authenticationSettings.JwtIssuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authenticationSettings.JwtKey)),
};
});
Like I said, the JWT token is successfully generated, but how can I send it to user and then catch it (for example in a "LoginSuccess" action) ?
I'm currently having an issues where a given client that has a valid claim identity specifying that the client is in the given role, not get authorised for actions and controllers that require that role.
I'm using an API to generate the token which the client will use to access both the API and the website, the tokens are generated as JWT.
I have looked on other posts that have this issue, but almost all of the are using identity which isn't required for this project, since we are using JWT.
I am using HttpContext.Signin to sign in the user, passing it the token received from the API which contains the user's roles and other claims.
I have included the code that's dealing with tokens, and the token received from the API -at the very end-. As clearly seen, the user has the role attribute, yet they are will not be authorised by the authentication middleware.
Creating the token
public async Task<string> CreateBearerTokenAsync(User user, string audience)
{
// Create identity for the client
var claims = await _clientManager.CreateUserClaimsIdentityAsync(user.Id);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = claims,
Expires = DateTime.UtcNow.AddHours(2),
IssuedAt = DateTime.UtcNow,
Issuer = "Bikefy Api",
Audience = audience,
SigningCredentials = _encryptionService.TokenSignKey
};
// Create the token
var token = new JwtSecurityTokenHandler().CreateJwtSecurityToken(tokenDescriptor);
// Write the token to string.
return new JwtSecurityTokenHandler().WriteToken(token);
}
Claims identity
public async Task<ClaimsIdentity> CreateUserClaimsIdentityAsync(Guid clientId)
{
if (clientId == null || Guid.Empty == clientId)
throw new ArgumentNullException($"{nameof(clientId)}");
var user = await GetClientByIdAsync(clientId);
var id = new ClaimsIdentity("ApiKey", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
var secKey = await GetSecurityStampAsync(clientId);
// Add default claims
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, clientId.ToString(), ClaimValueTypes.String));
if (user.FirstName != null)
id.AddClaim(new Claim(ClaimTypes.GivenName, user.FirstName, ClaimValueTypes.String));
if (user.LastName != null)
id.AddClaim(new Claim(ClaimTypes.Surname, user.LastName, ClaimValueTypes.String));
id.AddClaim(new Claim(IdentityProviderClaimType, "Bikey Identity", ClaimValueTypes.String));
id.AddClaim(new Claim(SecurityStampClaimType, secKey, ClaimValueTypes.String));
if (user.RoleName != null)
id.AddClaim(new Claim($"{nameof(User.RoleName)}", user.RoleName, ClaimValueTypes.String));
// Get roles
var roles = await GetRolesAsync(clientId);
foreach (var role in roles)
id.AddClaim(new Claim(ClaimTypes.Role, role));
// Add user claims
id.AddClaims(await GetClaimsAsync(clientId));
return id;
}
Cookie token configuration
public static void ConfigureAuthentication(this IServiceCollection services, IConfiguration configuration)
{
var secretKeys = new SecretKeys();
configuration.GetSection("SecretKeys").Bind(secretKeys);
var encryptService = new EncryptionService("00E7EB8C24190E2187", secretKeys);
services.AddSingleton(encryptService);
services.AddSingleton(secretKeys);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
x.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(c =>
{
c.Cookie.Name = "CityCyles.Auth";
c.Cookie.HttpOnly = true;
c.Cookie.Expiration = TimeSpan.FromDays(1);
c.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
c.LoginPath = $"/Account/Login";
c.LogoutPath = $"/Account/Logout";
c.AccessDeniedPath = $"/Account/AccessDenied";
});
}
User Login
try
{
result = await _apiProvider.SendPostRequest(_apiProvider.BuildUrl("Auth", "Authenticate"), apiModel);
// Convert the string into a token
var token = new JwtSecurityTokenHandler().ReadJwtToken(result);
// Create cookie options
var authOptions = new AuthenticationProperties()
{
AllowRefresh = model.Remember,
ExpiresUtc = DateTime.UtcNow.AddHours(2),
IssuedUtc = DateTime.UtcNow,
IsPersistent = true
};
// Get the user claims from the user
var claimsIdentity = new ClaimsIdentity(token.Claims, CookieAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role);
// Sign in the user
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authOptions);
// Redirect the user to the requested page or take them home
if (!string.IsNullOrEmpty(returnUrl))
return Redirect(returnUrl);
return RedirectToAction("Index", "Home");
}
catch (WebException ex)
{
_logger.LogWarning(LogEvents.HandlingLogin, ex, "Error authenticating user.");
//Display the error
ModelState.AddModelError("Custom-Error", ex.Message);
return View(model);
}
Controller Action
[HttpGet]
[Authorize(Roles = CityCyclesRoles.CityCyclesDocks)]
public async Task<IActionResult> OnBoard()
{
return View();
}
Token
{
"nameid": "3e637f01-85a9-4437-a113-50d2953d014e",
"given_name": "Stephanie",
"family_name": "Lee",
"http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider": "Bikey Identity",
"Bikeyfy.Identity.SecurityStamp": "1b6228cf-945a-4503-a64e-1dcb7b649c22",
"role": "CityCycles.Staff.Docks",
"nbf": 1552586386,
"exp": 1552593586,
"iat": 1552586386,
"iss": "CityCycles Api",
"aud": "CityCycles.Web"
}
I have a method to auth the user and create a token with expiration time, but if the token expired, the user cannot use the data. How to handle this?
This is my method:
[AllowAnonymous]
[HttpPost]
[Route("api/token")]
public IActionResult Post([FromBody]Personal personal)
{
string funcionID = "";
if (ModelState.IsValid)
{
var userId = GetUser(personal);
if (!userId.HasValue)
{
return Unauthorized();
}
else if (userId.Equals(2)) {
return StatusCode(404, "Vuelve a ingresar tu contraseƱa");
}
List<Claim> claims = new List<Claim>();
foreach (var funcion in Funcion) {
claims.Add(new Claim(ClaimTypes.Role, funcion.FuncionID.ToString()));
}
claims.Add(new Claim(JwtRegisteredClaimNames.Email, personal.CorreoE));
claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
var sesionExpira = new DatabaseConfig();
_configuration.GetSection("Database").Bind(sesionExpira);
var token = new JwtSecurityToken
(
issuer: _configuration["Issuer"],
audience: _configuration["Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(sesionExpira.Sesion),
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SigningKey"])),
SecurityAlgorithms.HmacSha256)
);
var token_email = token.Claims.Where(w => w.Type == "email").Select(s => s.Value).FirstOrDefault();
var token_rol = claims.Where(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role").Select(s => s.Value).FirstOrDefault();
var nombre = _context.Personal.Where(x => x.CorreoE == personal.CorreoE).Select(x => x.Nombre).FirstOrDefault();
return Ok(new { email = personal.CorreoE, token = new JwtSecurityTokenHandler().WriteToken(token), nombre = nombre, funcion = Funcion});
}
return BadRequest();
}
First, in the GetUser(Personal personal) method, that returns int, i return a number that i use to create a new token. Everything works fine, but i need some information to refresh the token if the time has expired
You can create middleware that will update the token. If you move your token creation logic to a separate service then you can do it like:
public class JwtTokenSlidingExpirationMiddleware
{
private readonly RequestDelegate next;
private readonly ITokenCreationService tokenCreationService;
public JwtTokenSlidingExpirationMiddleware(RequestDelegate next, ITokenCreationService tokenCreationService)
{
this.next = next;
this.tokenCreationService= tokenCreationService;
}
public Task Invoke(HttpContext context)
{
// Preflight check 1: did the request come with a token?
var authorization = context.Request.Headers["Authorization"].FirstOrDefault();
if (authorization == null || !authorization.ToLower().StartsWith("bearer") || string.IsNullOrWhiteSpace(authorization.Substring(6)))
{
// No token on the request
return next(context);
}
// Preflight check 2: did that token pass authentication?
var claimsPrincipal = context.Request.HttpContext.User;
if (claimsPrincipal == null || !claimsPrincipal.Identity.IsAuthenticated)
{
// Not an authorized request
return next(context);
}
// Extract the claims and put them into a new JWT
context.Response.Headers.Add("Set-Authorization", tokenCreationService.CreateToken(claimsPrincipal.Claims));
// Call the next delegate/middleware in the pipeline
return next(context);
}
}
And register it in Startup.cs:
public void Configure(IApplicationBuilder app)
{
...
app.UseMiddleware<JwtTokenSlidingExpirationMiddleware>();
...
}
I did something similiar to an old application using the method RefreshTokenAsync from IdentityModel.
You could try something like this when the User gets an unauthorized:
var identityService = await DiscoveryClient.GetAsync("http://localhost:5000");
// request token
var tokenClient = new TokenClient(identityService.TokenEndpoint, "client", "secret");
var tokenResponse = await tokenClient.RequestRefreshTokenAsync(refreshToken);
return Ok(new { success = true, tokenResponse = tokenResponse });
Source: https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/issues/4
EDIT: I have edited my original answer to provide a more clear and better answer according to the rules.
I've read the docs and followed the examples but I am unable to get user claims into the access token. My client is not ASP.NET core, so the configuration of the MVC client is not the same as the v4 samples.
Unless I have misunderstood the docs, the ApiResources are used to populate the RequestedClaimTypes in the profile service when creating the access token. The client should add the api resource to it's list of scopes to include associated userclaims. In my case they are not being connected.
When ProfileService.GetProfileDataAsync is called with a caller of "ClaimsProviderAccessToken", the requested claim types are empty. Even if I set the context.IssuedClaims in here, when it is called again for "AccessTokenValidation" the claims on the context are not set.
In the MVC app:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
UseTokenLifetime = false,
ClientId = "portal",
ClientSecret = "secret",
Authority = authority,
RequireHttpsMetadata = false,
RedirectUri = redirectUri,
PostLogoutRedirectUri = postLogoutRedirectUri,
ResponseType = "code id_token",
Scope = "openid offline_access portal",
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
await AssembleUserClaims(n);
},
RedirectToIdentityProvider = n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectRequestType.Logout)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
}
});
private static async Task AssembleUserClaims(AuthorizationCodeReceivedNotification notification)
{
string authCode = notification.ProtocolMessage.Code;
string redirectUri = "https://myuri.com";
var tokenClient = new TokenClient(tokenendpoint, "portal", "secret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(authCode, redirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoClient = new UserInfoClient(new Uri(userinfoendpoint), tokenResponse.AccessToken);
var userInfoResponse = await userInfoClient.GetAsync();
// create new identity
var id = new ClaimsIdentity(notification.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", notification.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", notification.AuthenticationTicket.Identity.FindFirst("sid").Value));
notification.AuthenticationTicket = new AuthenticationTicket(id, notification.AuthenticationTicket.Properties);
}
Identity Server Client:
private Client CreatePortalClient(Guid tenantId)
{
Client portal = new Client();
portal.ClientName = "Portal MVC";
portal.ClientId = "portal";
portal.ClientSecrets = new List<Secret> { new Secret("secret".Sha256()) };
portal.AllowedGrantTypes = GrantTypes.HybridAndClientCredentials;
portal.RequireConsent = false;
portal.RedirectUris = new List<string> {
"https://myuri.com",
};
portal.AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"portal"
};
portal.Enabled = true;
portal.AllowOfflineAccess = true;
portal.AlwaysSendClientClaims = true;
portal.AllowAccessTokensViaBrowser = true;
return portal;
}
The API resource:
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource
{
Name= "portalresource",
UserClaims = { "tenantId","userId","user" },
Scopes =
{
new Scope()
{
Name = "portalscope",
UserClaims = { "tenantId","userId","user",ClaimTypes.Role, ClaimTypes.Name),
},
}
},
};
}
The Identity resource:
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new IdentityResource[]
{
// some standard scopes from the OIDC spec
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource("portal", new List<string>{ "tenantId", "userId", "user", "role", "name"})
};
}
UPDATE:
Here is the interaction between the MVC app and the Identity Server (IS):
MVC:
Owin Authentication Challenge
IS:
AccountController.LoginAsync - assemble user claims and call HttpContext.SignInAsync with username and claims)
ProfileService.IsActiveAsync - Context = "AuthorizeEndpoint", context.Subject.Claims = all userclaims
ClaimsService.GetIdentityTokenClaimsAsync - Subject.Claims (all userclaims), resources = 1 IdentityResource (OpenId), GrantType = Hybrid
MVC:
SecurityTokenValidated (Notification Callback)
AuthorizationCodeReceived - Protocol.Message has Code and IdToken call to TokenClient.RequestAuthorizationCodeAsync()
IS:
ProfileService.IsActiveAsync - Context = "AuthorizationCodeValidation", context.Subject.Claims = all userclaims
ClaimsService.GetAccessTokenClaimsAsync - Subject.Claims (all userclaims), resources = 2 IdentityResource (openId,profile), GrantType = Hybrid
ProfileService.GetProfileDataAsync - Context = "ClaimsProviderAccessToken", context.Subject.Claims = all userclaims, context.RequestedClaimTypes = empty, context.IssuedClaims = name,role,user,userid,tenantid
ClaimsService.GetIdentityTokenClaimsAsync - Subject.Claims (all userclaims), resources = 2 IdentityResource (openId,profile), GrantType = authorization_code
MVC:
call to UserInfoClient with tokenResponse.AccessToken
IS:
ProfileService.IsActiveAsync - Context = "AccessTokenValidation", context.Subject.Claims = sub,client_id,aud,scope etc (expecting user and tenantId here)
ProfileService.IsActiveAsync - Context = "UserInfoRequestValidation", context.Subject.Claims = sub,auth_time,idp, amr
ProfileService.GetProfileDataAsync - Context = "UserInfoEndpoint", context.Subject.Claims = sub,auth_time,idp,amp, context.RequestedClaimTypes = sub
As I'm not seeing what happens in your await AssembleUserClaims(context); I would suggest to check if it is doing the following:
Based on the the access token that you have from either the context.ProtoclMessage.AccessToken or from the call to the TokenEndpoint you should create a new ClaimsIdentity. Are you doing this, because you are not mentioning it?
Something like this:
var tokenClient = new TokenClient(
IdentityServerTokenEndpoint,
"clientId",
"clientSecret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaims(n.AuthenticationTicket.Identity.Claims);
// get user info claims and add them to the identity
var userInfoClient = new UserInfoClient(IdentityServerUserInfoEndpoint);
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
var userInfoEndpointClaims = userInfoResponse.Claims;
// this line prevents claims duplication and also depends on the IdentityModel library version. It is a bit different for >v2.0
id.AddClaims(userInfoEndpointClaims.Where(c => id.Claims.Any(idc => idc.Type == c.Type && idc.Value == c.Value) == false));
// create the authentication ticket
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
And one more thing - read this regarding the resources. In your particular case, you care about IdentityResources (but I see that you also have it there).
So - when calling the UserInfoEndpoint do you see the claims in the response? If no - then the problem is that they are not issued.
Check these, and we can dig in more.
Good luck
EDIT
I have a solution that you may, or may not like, but I'll suggest it.
In the IdentityServer project, in the AccountController.cs there is a method public async Task<IActionResult> Login(LoginInputModel model, string button).
This is the method after the user has clicked the login button on the login page (or whatever custom page you have there).
In this method there is a call await HttpContext.SignInAsync. This call accept parameters the user subject, username, authentication properties and list of claims. Here you can add your custom claim, and then it will appear when you call the userinfo endpoint in the AuthorizationCodeReceived. I just tested this and it works.
Actually I figured out that this is the way to add custom claims. Otherwise - IdentityServer doesn't know about your custom claims, and is not able to populate them with values. Try it out and see if it works for you.
You need to modify the code of "Notifications" block in MVC App like mentioned below:
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n => {
var userInfoClient = new UserInfoClient(UserInfoEndpoint);
var userInfoResponse = await userInfoClient.GetAsync(n.ProtocolMessage.AccessToken);
var identity = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
identity.AddClaims(userInfoResponse.Claims);
var tokenClient = new TokenClient(TokenEndpoint, "portal", "secret");
var response = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
identity.AddClaim(new Claim("access_token", response.AccessToken));
identity.AddClaim(new Claim("expires_at", DateTime.UtcNow.AddSeconds(response.ExpiresIn).ToLocalTime().ToString(CultureInfo.InvariantCulture)));
identity.AddClaim(new Claim("refresh_token", response.RefreshToken));
identity.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
n.AuthenticationTicket = new AuthenticationTicket(identity, n.AuthenticationTicket.Properties);
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token").Value;
n.ProtocolMessage.IdTokenHint = idTokenHint;
}
return Task.FromResult(0);
}
}
(consider if any changes related to the version of identity server as this code was built for identity server 3.)
Why do you have "portal" listed as an identity resource and Api resource? That could be causing some confusion.
Also, before I switched to IdentityServer4 and asp.net core, my IdentityServer3 startup code looked very similar to what you have with MVC. You may want to look at the examples for IdentityServer3.
Some suggestions I may give, in your "ResponseType" field for MVC, you could try "code id_token token"
Also, you are setting your claims on AuthorizationCodeReceived, instead use SecurityTokenValidated.
But you shouldn't have to do anything custom like people are mentioning. IdentityServer4 handles custom ApiResources like you are attempting to do.
You can try to implement your own IProfileService and override it following way:
services.AddIdentityServer()
.//add clients, scopes,resources here
.AddProfileService<YourOwnProfileProvider>();
For more information look up here:
https://damienbod.com/2016/10/01/identityserver4-webapi-and-angular2-in-a-single-asp-net-core-project/
portal is not an identity resource: you should remove
new IdentityResource("portal", new List{ "tenantId",
"userId", "user", "role", "name"})
Names for the api resources should be consistent:
public static IEnumerable GetApiResources()
{
return new List
{
new ApiResource
{
Name= "portal",
UserClaims = { "tenantId","userId","user" },
Scopes =
{
new Scope("portal","portal")
}
},
};
}
Try setting GrantTypes.Implicit in the client.
I use third-party auth nuget instagram package for login and set new claim:
app.UseInstagramAuthentication(new InstagramAuthenticationOptions
{
ClientId = "XXXXXXXXXXXXXXXXXXXXXXXXX",
ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXX",
Provider = new InstagramAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim("urn::instagram::accesstoken", context.AccessToken));
return Task.FromResult(0);
}
}
but when I try to get this claim
var ctx = HttpContext.GetOwinContext();
ClaimsPrincipal user = ctx.Authentication.User;
IEnumerable<Claim> claims = user.Claims;
This claim does not exist in the list. Why?
You need to retrieve and store those claims on external login, maybe something like:
private async Task StoreAuthTokenClaims(ApplicationUser user)
{
// Get the claims identity
ClaimsIdentity claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (claimsIdentity != null)
{
// Retrieve the existing claims
var currentClaims = await UserManager.GetClaimsAsync(user.Id);
// Get the list of access token related claims from the identity
var tokenClaims = claimsIdentity.Claims
.Where(c => c.Type.StartsWith("urn:tokens:"));
// Save the access token related claims
foreach (var tokenClaim in tokenClaims)
{
if (!currentClaims.Contains(tokenClaim))
{
await UserManager.AddClaimAsync(user.Id, tokenClaim);
}
}
}
}
And on the ExternalLoginConfirmation method:
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await StoreAuthTokenClaims(user);
// Sign in and redirect the user
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
After that, you can retrieve claims like:
var claimsIdentity = HttpContext.User.Identity as ClaimsIdentity;
if (claimsIdentity != null)
{
var claims = claimsIdentity.Claims;
}