Identity Server 3 implicit grant, role based authorization - asp.net-web-api2

I have set up the Identity Server 3 with Membership reboot database as my authorization server and have also developed a Web Api project which will be accessed by a javascript web app.
Using the implicit flow, the client is able to log in and obtain id_token and access_token. Now I have a few questions, which I would appreciate some detailed answers too:
What is the functionality of id_token? After obtaining it, what can I do with it?
The roles of the users are stored in the database as claims (like for example, the key value of "role","admin"). How do I perform the role-based authorization at this point? It seems like the id_token contains those claims but the access_token does not. When sending my access_token as Bearer along my Api request, how does the api know which roles the sending user has?
In a web api controller, I want to access the user's information using:
var user = User as ClaimsPrincipal;
using this code, I cannot get pretty much anything about the user; username, id, etc. Also when I use user.Claims in the controller, I have no access to the claims stored in the database. How are there two sets of claims, one in the database one in the token?!
Any extra information is greatly appreciated.

id_token should be used in the client. You can use it to access the claims at client side. AccessToken is to be used at the API.
To the claims to be included in the access_token you need to create a scope with relevant claims and request that scope in the request.
To create a scope(in the self-host sample add scope to Scopes.cs):
new Scope
{
Name = "myApiScope",
DisplayName = "IdentityManager",
Type = ScopeType.Resource,
Emphasize = true,
ShowInDiscoveryDocument = false,
Claims = new List<ScopeClaim>
{
new ScopeClaim(Constants.ClaimTypes.Name),
new ScopeClaim(Constants.ClaimTypes.Role)
}
}
Ask for the scope in your authorization request(In Javascript implicit client - simple it is done as follows)
function getToken() {
var authorizationUrl = 'https://localhost:44333/core/connect/authorize';
var client_id = 'implicitclient';
var redirect_uri = 'http://localhost:37045/index.html';
var response_type = "token";
var scope = "myApiScope";
var state = Date.now() + "" + Math.random();
localStorage["state"] = state;
var url =
authorizationUrl + "?" +
"client_id=" + encodeURI(client_id) + "&" +
"redirect_uri=" + encodeURI(redirect_uri) + "&" +
"response_type=" + encodeURI(response_type) + "&" +
"scope=" + encodeURI(scope) + "&" +
"state=" + encodeURI(state);
window.location = url;
}
This will include Name and Role claims in your access token
Configure your API with relevant middleware in the web API startup(in SampleAspNetWebApi sample it is done as follows)
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44333/core",
RequiredScopes = new[] { "myApiScope" }
});
Then you can access the claims as follows
var principal = User as ClaimsPrincipal;
return from c in principal.Identities.First().Claims
select new
{
c.Type,
c.Value
};

Related

IdentityServer4 - Best Practises to get User Information from access token

I recently started developing using IdentityServer4. What I want to achieve is to have a number of independent web applications that use the same authorization server, my identity server.
My problem is how to make sure, that all my independend web applications have obtained and display the up to date user information (like firstName,lastName,avatar etc) which are stored in my IdentityServer4 database
I am aware that I should implement the IProfileService interface, to make sure that user-info endpoint will return all additional user info, but I dont know where to call this api request from my web applications. I have created a function that looks like this:
var t = await HttpContext.GetTokenAsync("access_token");
if (!String.IsNullOrEmpty(t))
{
var client = new HttpClient();
var userInfoRequest = new UserInfoRequest()
{
Address = "https://localhost:5001/connect/userinfo",
Token = t
};
var response = client.GetUserInfoAsync(userInfoRequest).Result;
if (response.IsError)
throw new Exception("Invalid accessToken");
dynamic responseObject = JsonConvert.DeserializeObject(response.Raw);
string firstName = responseObject.FirstName.ToString();
HttpContext.Session.SetString("User_FirstName", firstName);
string lastName = responseObject.LastName.ToString();
HttpContext.Session.SetString("User_LastName", lastName);
HttpContext.Session.SetString("User_FullName", firstName + " " + lastName);
if (responseObject.Image != null && !String.IsNullOrEmpty(responseObject.Image.ToString()))
{
string im = responseObject.Image.ToString();
HttpContext.Session.SetString("User_Image", im);
}
}
to get user Info from web applications.
My problem is when and how to call this function, every time the user redirects logged in from identity server to my web application, and how to make sure that Sessions will keep user associated data, for as much as the user remains logged in to my web application.
You can call Token Introspection endpoint to get all user info from #identityServer4.

Hangout OAuth - Invalid Scope : Some requested scopes cannot be shown

I am facing the below error while generating token for service account for the Hangout Scope - https://www.googleapis.com/auth/chat.bot.
Where i receive 400 response code after making a post request to this url -
https://www.googleapis.com/oauth2/v4/token
the params are
Content-Type:application/x-www-form-urlencoded
httpMode:POST
body:grant_type=jwt-bearer&assertion=assertion-token
Note:This was completely working fine. Suddenly am facing this issue.
cross verified: jwt generation,service_account_id and etc...
Error Response : { "error": "invalid_scope", "error_description": "Some requested scopes cannot be shown": [https://www.googleapis.com/auth/chat.bot]}
code for generating assertion:
//FORMING THE JWT HEADER
JSONObject header = new JSONObject();
header.put("alg", "RS256");
header.put("typ", "JWT");
//ENCODING THE HEADER
String encodedHeader = new String(encodeUrlSafe(header.toString().getBytes("UTF-8")));
//FORMING THE JWT CLAIM SET
JSONObject claimSet = new JSONObject();
claimSet.put("iss","123#hangout.iam.gserviceaccount.com");
claimSet.put("sub","one#domain.com");
claimSet.put("scope","https://www.googleapis.com/auth/chat.bot");
claimSet.put("aud","https://oauth2.googleapis.com/token");
long time = System.currentTimeMillis() / 1000;
claimSet.put("exp",time+3600);
claimSet.put("iat",time);
//ENCODING THE CLAIM SET
String encodedClaim = new String(encodeUrlSafe(claimSet.toString().getBytes("UTF-8")));
//GENERATING THE SIGNATURE
String password = "secretofkey", alias = "privatekey";
String signInput = encodedHeader + "." + encodedClaim;
Signature signature = Signature.getInstance("SHA256withRSA");
String filepath = "/check/PrivateKeys/hangoutPKEY.p12";
KeyStore kstore = KeyStore.getInstance("PKCS12");
fis = new FileInputStream(filepath);
kstore.load(fis, password.toCharArray());
KeyStore.PrivateKeyEntry pke = (KeyStore.PrivateKeyEntry) kstore.getEntry(alias, new KeyStore.PasswordProtection(password.toCharArray()));
PrivateKey pKey = pke.getPrivateKey();
signature.initSign(pKey);
signature.update(signInput.getBytes("UTF-8"));
String encodedSign = new String(encodeUrlSafe(signature.sign()), "UTF-8");
//JWT GENERATION
String JWT = signInput + "." + encodedSign;
String grant_type = URLEncoder.encode("urn:ietf:params:oauth:grant-type:jwt-bearer");
reqBody = "grant_type=" + grant_type + "&assertion=" + JWT;
public static byte[] encodeUrlSafe(byte[] data) {
Base64 encoder = new Base64();
byte[] encode = encoder.encodeBase64(data);
for (int i = 0; i < encode.length; i++) {
if (encode[i] == '+') {
encode[i] = '-';
} else if (encode[i] == '/') {
encode[i] = '_';
}
}
return encode;
}
Does anyone have any idea, where am going wrong?
Short answer:
You are trying to use domain-wide authority to impersonate a regular account. This is not supported in Chat API.
Issue detail:
You are using the sub parameter when building your JWT claim:
claimSet.put("sub","one#domain.com");
Where sub refers to:
sub: The email address of the user for which the application is requesting delegated access.
I noticed that, if I add the sub parameter to my test code, I get the same error as you.
Solution:
Remove this line from your code in order to authorize with the service account (without impersonation) and handle bot data:
claimSet.put("sub","one#domain.com");
Background explanation:
Chat API can be used for bots to manage their own data, not to manage end-user data. Therefore, you can only use a service account to act as the bot, without impersonating an end-user.
From this Issue Tracker comment:
At the present moment, Chat API can only be used to manage bot-related data (listing the spaces in which the bot is included, etc.). Using domain-wide delegation to manage regular users' data is not currently possible.
Feature request:
If you'd like to be able to access regular users' data with your service account and domain-wide delegation via Chat API, you are not alone. This feature has been requested before in Issue Tracker:
Accessing the Google Chats of regular users using domain-wide delegated permission and service account credentials
I'd suggest you to star the referenced issue in order to keep track of it and to help prioritizing it.
Reference:
Using service accounts
Delegating domain-wide authority to the service account
Preparing to make an authorized API call

How to attach needed claims to tokens with policy-based authorization?

I am trying to implement policy-based authorization in my Web API. What I am trying to figure out is how to determine which claims should be added to the token when generating it for the user on his/her log-in operation. Should I store information about claims for each user in the database, or I am misunderstanding some concepts?
Here is the method I use to generate JWT/refresh-token pair:
public async Task<AuthenticationResponse> GenerateTokenPairForUserAsync(User user)
{
var jwtTokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtConfig.Secret);
var guid = Guid.NewGuid().ToString();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(ClaimTypes.Sid, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(ClaimTypes.Role, user.RoleId.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, guid)
}),
Expires = DateTime.UtcNow.Add(_jwtConfig.TokenLifetime),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
var token = jwtTokenHandler.CreateToken(tokenDescriptor);
var jwtToken = jwtTokenHandler.WriteToken(token);
var refreshToken = new RefreshToken
{
JwtId = token.Id,
IsUsed = false,
IsRevoked = false,
UserId = user.Id,
CreationDate = DateTime.UtcNow,
ExpiryDate = DateTime.UtcNow.Add(_refreshTokenConfig.TokenLifetime),
Token = RandomString(25) + Guid.NewGuid()
};
await _refreshTokenRepository.CreateAsync(refreshToken);
return new AuthenticationResponse
{
Token = jwtToken,
Success = true,
RefreshToken = refreshToken.Token
};
}
You need a few things to achieve that:
You should have a mapping of which clients can receive which claims. Clients, meaning the apps which call your authorization endpoint. If you only have one, then this is not a problem, but if you have many, you should keep somewhere in a database a list of all the claims that the given client should receive in the token.
It's convenient to have a mapping of scopes to claims. In an authorization request you can then request scopes, which are essentially groups of claims. You will need that if your clients (or client) can actually request tokens with different scopes. E.g. you might want to request a token which can be used to perform some more sensitive operations, maybe change user's email. Then you can ask the server to issue the token maybe with a scope "admin", which translates to, among others, a claim can_change_email: true. This claim can then be used to perform authorization decisions.
Finally you need to know, for every claim, what is the source of data. So, once you know that your tokens must contain claims claim1, claim2 and claim3, then you must know where to take the data from. This can be hardcoded - e.g. you implement a getValueForClaim2() method which knows that it should read data from a database (e.g. it's a user's phone number). Or you can create some more sophisticated solutions, where you keep some mappings to claimProviders, then implement those providers. In the end, where do you get the data from it's totally up to you - this can be a database, a file, maybe an API call, or the value is calculated based on some input.
Have a look at these resources about claims that we wrote at Curity: https://curity.io/resources/claims/ if you want to learn more about this topic.

How to add and Persist a new ClaimsIdentity with custom claims to a ClaimsPrincipal after authentication by an OpenId Connect provider

I'm using OpenId Connect with .NetCore to send users to an external OpenId Connect authentication provider (OP) which returns relevant access tokens and an authenticated ClaimsPrinciple which has claims from the OP in such as Name, Email Address, Customer Id, etc. What I want to be able to do is once the User has been authenticated and the ClaimsPrinciple is returned from the OP, I would like to add a ClaimsIdentities with custom claims for each licence the user holds on there account to the ClaimsPrinciple. So when a user switches between their account licences I can access the correct identity for that licence and provide access to features based on the custom claims. Currently, I can add the custom claims to a ClaimsIdentity then add the ClaimsIdentity to the ClaimsPrinciple but the new identities are not persisted and added into the Cookie.
So A User can have multiple licences.
For each licence I want to add a ClaimsIdentity.
Then I want to persist the changes to the ClaimsPrinciple by using a cookie.
Here is a code snippet which will hopefully add some context. This is for the Login method in my application which is hit once the OP has authenticated the user.
public async Task<IActionResult> Login()
{
string token = HttpContext.Authentication.GetTokenAsync("access_token").Result;
string refreshToken = HttpContext.Authentication.GetTokenAsync("refresh_token").Result;
// ControllerBase User class this is the User I want to add the identites to, I think.
string userGuid = User.Claims.Where(c => c.Type == "Guid").FirstOrDefault().Value;
UserTokens userTokens = new UserTokens
{
LastUpdatedDate = DateTime.Now,
UserGuid = userGuid,
UserAccessToken = token,
RefreshToken = refreshToken
};
await _busClient.PublishAsync<UpdateUserTokens>(new UpdateUserTokens(userTokens));
// Domain Model User, a different User to the controllerBase User
// this is how a user is represented in my application but this has
// no control over authentication and claims
User user = await _requestClient.RequestAsync<UserGuidRequest, User>(new UserGuidRequest(userGuid));
var userLicences = await _requestClient.RequestAsync<UserLicenceRequest, List<UserLicence>>(new UserLicenceRequest(userGuid));
var identityServerUserClaims = User.Claims.ToList();
foreach(UserLicence userLicence in userLicences)
{
List<Claim> userLicenceIdentityClaims = new List<Claim>();
foreach (Claim claim in identityServerUserClaims)
{
userLicenceIdentityClaims.Add(claim);
}
userLicenceIdentityClaims.Add(new Claim(userLicence.RoleType.ToString(), ""));
var userLicenceIdentity = new ClaimsIdentity(userLicenceIdentityClaims, User.Identity.AuthenticationType);
userLicenceIdentity.Label = userLicence.Id.ToString();
User.AddIdentity(userLicenceIdentity);
}
//TODO: either need to sign out then back in or save new user claims to cookies somehow?
return View("Index", user);
}
If anyone could help it would be greatly appreciated. If you need any more information please ask and I'll try to provide whatever I can, hopefully, this is a good start.

Adding extra details to a webapi bearer token

I am trying to learn the new webapi2.1 authentication pieces.
I have got the bearer token wired up and working with my webapi. My next thing I would like to do is be able to store some additional information within the token (if possible) so when the client sends back the token I can retrieve the details without the need of them sending multiple values.
Can the token be extended to contain custom data?
Sorry if the question is a little vague but I have had a big hunt around and can't seem to find any further information
Thank you
Since the token is signed with a "secret" key - only the issuer can add data to it.
You can amend something to the claim set after receiving the token in your Web API - this is called claims transformation.
I have a sample of it here:
https://github.com/thinktecture/Thinktecture.IdentityModel/tree/master/samples/OWIN/AuthenticationTansformation
In essence you are writing some code that inspects the incoming token and add application specific claims to the resulting principal.
// Transform claims to application identity
app.UseClaimsTransformation(TransformClaims);
private Task<ClaimsPrincipal> TransformClaims(ClaimsPrincipal incoming)
{
if (!incoming.Identity.IsAuthenticated)
{
return Task.FromResult<ClaimsPrincipal>(incoming);
}
// Parse incoming claims - create new principal with app claims
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, "foo"),
new Claim(ClaimTypes.Role, "bar")
};
var nameId = incoming.FindFirst(ClaimTypes.NameIdentifier);
if (nameId != null)
{
claims.Add(nameId);
}
var thumbprint = incoming.FindFirst(ClaimTypes.Thumbprint);
if (thumbprint != null)
{
claims.Add(thumbprint);
}
var id = new ClaimsIdentity("Application");
id.AddClaims(claims);
return Task.FromResult<ClaimsPrincipal>(new ClaimsPrincipal(id));
}