I am having trouble getting the users Identity information from within the API.
My project consists of a standalone WASM app, IDP and WebApi.
I have everything setup and it works but what I am after is a Call from the Blazor client to get some data from the api. The Api then uses the users email address to identify them and get the data just for them.
I have looked at similar questions and the solutions don't work for me on my project.
[HttpGet("GetData")]
public async Task<IActionResult> GetData()
{
string test = User.Identity.Name; // returns null
string username = "myuser#users.com";
List<string> data= new List<string>();
data= (await _dataRepository.GetData(username)).ToList();
if (data.Count > 0)
{
return Ok(data);
}
else
{
return NoContent();
}
}
So where I am setting the username is there a way to get a hold of the email of the user who passed the request?
Edited
Access Token:
{
"alg": "RS256",
"kid": "2D49329C75FC43C78590AF6F6A0EFDB2",
"typ": "at+jwt"
}
{
"nbf": 1639243158,
"exp": 1639246758,
"iss": "https://localhost:5000",
"aud": "https://localhost:5000/resources",
"client_id": "ATS",
"sub": "4892725f-f6da-4a28-827a-ce666bb6f098",
"auth_time": 1638729064,
"idp": "local",
"jti": "53CB2F8FCB2EB34E3501E2C210B59B5D",
"sid": "8463E4AA74D7369C1176249ED8FA46B1",
"iat": 1639243158,
"scope": [
"openid",
"profile",
"MY_API"
"email"
],
"amr": [
"pwd"
]
}
First you would typically secure your controller action methods using the [Authorize(...)] attribute and lookup the authorization in ASP.NET Core for more details about that.
Second, the most common problem when the name/email is not found is that you need to turn of the claims mapping using
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
This is because by default Microsoft and OpenID have different opinions on what the claim names should be and because of that it can be wise to first clear this mapping and then secondly, point tell Microsoft what the name of the name/role claim by setting this:
opt.TokenValidationParameters.RoleClaimType = "roles";
opt.TokenValidationParameters.NameClaimType = "name";
For more details about claims mapping visit:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims?view=aspnetcore-6.0
Related
I m trying to create a Spring Boot application that a user can select type of role on registration e.g patient/doctor
The roles are created automatically in the database on application start
I m using Postman to make registration request but when I pass the role it is duplicated in the database
Any advice would be much appreciated!
Here is the method i use to create a user
public User createUser (RegistrationRequest registrationRequest) {
User user = new User();
user.setFirstname(registrationRequest.getFirstname());
user.setLastname(registrationRequest.getLastname());
user.setEmail(registrationRequest.getEmail());
user.setUsername(registrationRequest.getUsername());
user.setPhone(registrationRequest.getPhone());
user.setAddress(registrationRequest.getAddress());
user.setPassword(passwordEncoder.encode(registrationRequest.getPassword()));
user.setCreatedAt(Instant.from(Instant.now()));
user.setIsEnabled(false);
user.setRoles(registrationRequest.getRoles());
return user;
and here is the json request
{
"firstname":"Mary",
"lastname":"K",
"email":"example#gmail.com",
"username":"rain",
"password":"123",
"phone":"2848392",
"address":
{
"city": "Burdwan",
"region": "Paschimbanga",
"street": "gwg",
"zipcode": "713102"
},
"roles":[
{
"name":"DOCTOR_ROLE"
} ]
}
In my .Net Core web API protected by IdentityServer4, I need to decide what identity provider (Google, Windows, or local, for instance) authenticated the user. So far, I am not sure how to do that.
If I search for idp claim from access_token in a controller, as shown below, I can see the claim value correctly
var accessToken = await HttpContext.GetTokenAsync("access_token");
var token = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
var claim = token.Claims.First(c => c.Type == "idp").Value;
But if I try to find it using AuthorizationHandlerContext in a non-controller class in API as following, as shown in code below, it is not there
var identity = context.User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
// var v = identity.FindFirst("idp").Value;
}
So looks like that idp is indeed in the token, it just not accessible from the non-controller class where it is needed. How do I get idp from non-controller class in API?
UPDATE - 1
Here is my ConfigureService in my API
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true; // test only
services.AddControllers();
services.AddControllers()
.AddNewtonsoftJson(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
});
services.Configure<QLHostOptions>(Configuration.GetSection(QLHostOptions.Host));
services.AddAuthentication("Bearer").AddJwtBearer("Bearer", options =>
{
options.Authority = Configuration.GetSection(QLHostOptions.Host).Get<QLHostOptions>().IdentityGateway;
options.SaveToken = true;
// test only
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
}).AddOpenIdConnect(options =>
{
options.ClaimActions.Remove("aud");
});
services.AddTransient<IAuthorizationPolicyProvider, QLPolicyProvider>();
services.AddTransient<IAuthorizationHandler, QLPermissionHandler>();
services.AddTransient<gRPCServiceHelper>();
}
UPDATE-2
Changed ...Remove("idp") to inside AddJwtBearer, as Tory suggested, but it doesn't take it (see screenshot below):
and here is the access token from API
"eyJhbGciOiJSUzI1NiIsImtpZCI6IjBFM0Y2MkRGMTdFQUExQURFRTc1NDQzQzQ0M0YxRkU2IiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NDAwMzExMDcsImV4cCI6MTY0MDAzNDcwNywiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NjAwNSIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjYwMDUvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoibXZjIiwic3ViIjoiZGM0YWI1OGMtNGVjMC00ZTAyLWIxM2YtYzEyYzk1MzJlNzcyIiwiYXV0aF90aW1lIjoxNjQwMDMxMTA2LCJpZHAiOiJHb29nbGUiLCJBc3BOZXQuSWRlbnRpdHkuU2VjdXJpdHlTdGFtcCI6IjJEQ0hXNVRER1E3NDNSUEpOWE43SVJIWlRIVllIUTRJIiwibmFtZSI6IkxpZmVuZyBYdSIsImVtYWlsIjoibGlmZW5neHUyNkBnbWFpbC5jb20iLCJyb2xlIjoiUUxBZG1pbiIsInByZWZlcnJlZF91c2VybmFtZSI6IjM1ZGJkMmY2LTlmNDUtNDJhYy04M2EzLTgzZmUyMTFjNTNiNSIsIklzRW5hYmxlZCI6IlRydWUiLCJRaWNMaW5rVUlEIjoiIiwianRpIjoiQzE3Qjc2QzQ0NjA4MzkxMDBENEExMEM4Q0YwQzA1NDEiLCJzaWQiOiIzMEY5NTA5NzQ3OUUxMzAyMUVBQTdDOTAzNzg4MDcxNiIsImlhdCI6MTY0MDAzMTEwNywic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiUWljTGlua0NJRCIsIlFpY0xpbmtVSUQiLCJyb2xlcyIsIklzRW5hYmxlZCIsIkxpZmVuZ0FQSSIsIlFpY0xpbmtBUEkiLCJvZmZsaW5lX2FjY2VzcyJdLCJhbXIiOlsiZXh0ZXJuYWwiXX0.boZCqYImWfkE48X5UgFOAAz9bR6CH2cwAYHGd4Ykg0vDH9qnYdje5Zmqov4HpINsu_rt16zxAX_JCEn0hvdznXK2NQyZSBGsjF0tcMgtOY0__kAfhpOT-fORakiIjeMWIKG7tPEHCxSib0wNuMNw6i3o1giAnPt0ch2DH0fBtaEYkq4MRKMCteFuqbX0cogXIuMewNywMvrHv4_MixhMy3L8_xIwFvTZ67jhUn4Fd5X58-jc-RPNudcP95XIjzHm9OzWfgegV1IAKjsv98XEYX1pUxm-nrOMgYWxEJSyxEpp0L_9RzKTr_LZ-ep-x5QRvVewgiozJV3mse0pHgTjbw"
By default many of the more internal claims in a token are removed from the User ClaimsPrinicpal claims.
If you want to get a specific claim into your user, you can use in the client:
}).AddOpenIDConnect(options =>
{
//Will result in that the aud claim is not removed.
options.ClaimActions.Remove("idp");
...
secondly, some of the claims are renamed and if you want to disable that renaming, you can add:
// By default, Microsoft has some legacy claim mapping that converts
// standard JWT claims into proprietary ones. This removes those mappings.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
For the API you should not need to do anything special to get the idp claim. I just ran a test with this setup in .NET 5:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMyJwtBearer(opt =>
{
opt.IncludeErrorDetails = true;
opt.MapInboundClaims = false;
opt.TokenValidationParameters.RoleClaimType = "role";
opt.TokenValidationParameters.NameClaimType = "name";
opt.Audience = "paymentapi";
opt.Authority = "https://localhost:6001";
});
services.AddControllers();
}
I did give it a test on .NET 5 and if I have this access token:
{
"nbf": 1640033816,
"exp": 1640037416,
"iss": "https://localhost:6001",
"aud": "paymentapi",
"client_id": "clientcredentialclient",
"managment": "yes",
"email": "tore#tn-data.se",
"name": "tore nestenius",
"idp": "Google",
"role": [
"admin",
"developer",
"support"
],
"website": "https://www.tn-data.se",
"jti": "5DC46A29372031F0AA6F7B62B5FDCCD6",
"iat": 1640033816,
"scope": [
"payment"
]
}
Then my user in my API controller contains the idp claim:
I am new to plaid.
I created a plaid access_token and now its showing
"error_code":"ITEM_LOGIN_REQUIRED"
Using the doc I understand that we need to use update mode for solving this
then access token will not change and no need to call token -exchange
after getting this error
I tried calling
https://sandbox.plaid.com/link/token/create
method -POST
{
"client_id": "xxxxxx",
"secret": "xxxxxx",
"client_name": "test",
"user": { "client_user_id": "xxxx" },
"country_codes": ["US"],
"language": "en",
"access_token": "access-sandbox-xxxx-xxx-xxx-xxx-111111"
}
then I got new link_token
{
"expiration": "2021-11-09T13:46:12Z",
"link_token": "link-sandbox-xxxx-xxx-xxxx-xxx-xxx",
"request_id": "xxxxx"
}
Then after what I need to do ?? .. I understand that no need to do token exchange api.
but if I tried to use this api using the existing access-token it is showing the same error
https://sandbox.plaid.com/accounts/get
method -POST
{
"client_id": "xxxxxx",
"secret": "xxxxxx",
"access_token": "access-sandbox-xxxx-xxx-xxx-xxx-111111"
}
output
{
"display_message": null,
"error_code": "ITEM_LOGIN_REQUIRED",
"error_message": "the login details of this item have changed (credentials, MFA, or required user action) and a user login is required to update this information. use Link's update mode to restore the item to a good state",
"error_type": "ITEM_ERROR",
"request_id": "3LMjpQHxYAMDwos",
"suggested_action": null
}
in that document they are saying like this.
An Item's access_token does not change when using Link in update mode, so there is no need to repeat the exchange token process.
then why I am getting again this ??
What I need to do solve this issue?
// Initialize Link with the token parameter
// set to the generated link_token for the Item
const linkHandler = Plaid.create({
token: 'GENERATED_LINK_TOKEN',
onSuccess: (public_token, metadata) => {
// You do not need to repeat the /item/public_token/exchange
// process when a user uses Link in update mode.
// The Item's access_token has not changed.
},
onExit: (err, metadata) => {
// The user exited the Link flow.
if (err != null) {
// The user encountered a Plaid API error prior
// to exiting.
}
// metadata contains the most recent API request ID and the
// Link session ID. Storing this information is helpful
// for support.
},
});
After getting the Link token, you need to initialize Link with the Link token. Per the docs:
"To use update mode for an Item, initialize Link with a link_token configured with the access_token for the Item that you wish to update."
https://plaid.com/docs/link/update-mode/
Once the user has successfully completed the Link flow, the access token should be reactivated.
Since Google is deprecating Google contacts API and instead advising us to use Google People API to add/create/delete contacts. I was able to create, get Google contacts, Sample code is below:
const { google } = require("googleapis")
const path = require("path")
const keyFile = path.join(__dirname, "serviceAccCredentials.json")
const scopes = [
"https://www.googleapis.com/auth/contacts",
"https://www.googleapis.com/auth/contacts.readonly"
]
function log(arg) {
console.log(JSON.stringify(arg, null, 4))
}
const run = async () => {
try {
const { people, contactGroups } = google.people({
version: "v1",
auth: await google.auth.getClient({
keyFile,
scopes
})
})
const createContact = await people.createContact(
{
requestBody: {
names: [
{
givenName: "Yacov 3",
familyName: "110$"
}
],
"memberships": [
{
"contactGroupMembership": {
contactGroupId: 'myContacts'
// "contactGroupResourceName": "contactGroups/myContacts"
}
}
]
}
}
)
log(createContact.data)
const afterResponse = await people.connections.list({
resourceName: "people/me",
personFields: "names",
})
log(afterResponse.data)
} catch (e) {
console.log(e)
}
}
run()
Problem is that i don't see the contacts created with the service account under the Google contacts. Normally the service account is created for the G-suit user, under the G-suit domain wide delegation settings, i added the project id with scope as well. Also People API is enabled in the service account.
Further, In the playground area of Google's official documentation when i tried to create the a Google contact, it worked. The request from there API explorer / playground looks like this
const createContact = await people.createContact({
"personFields": "names",
"sources": [
"READ_SOURCE_TYPE_CONTACT"
],
"prettyPrint": true,
"alt": "json",
"resource": {
"names": [
{
"givenName": "test 2",
"familyName": "playground"
}
],
"memberships": [
{
"contactGroupMembership": {
"contactGroupResourceName": "contactGroups/myContacts"
}
}
]
}
})
Strangely, all these properties like contactGroupResourceName, personFields, sources, alt, prettyPrint doesn't exists.
can anyone really tell me what is going on.
PS: i can not and don't want to use OAuth2 since the application is going to be server to server communication, wouldn't involve any human consent. Thanks
Issue:
You might have enabled domain-wide delegation for your service account, but you are not using it to impersonate a regular user.
The purpose of domain-wide delegation is for the service account to act on behalf of any user in the domain, but in order to do that, you have to specify which user you want the service account to impersonate.
Otherwise, the service account will access its own resources (its Contacts, its Drive, its Calendar, etc.) not the resources of a regular account. Therefore, you'll not see the created contacts if you access Contacts UI with a regular account, since contacts were not created for this account.
Solution:
You need to impersonate the account for which you want to create contacts.
In order to do that, since you're using Node's getClient(), you should specify the email address of the account you want to impersonate, as shown here:
auth.subject = "email-address-to-impersonate";
Update:
In this case, you could do the following:
let auth = await google.auth.getClient({
keyFile,
scopes
});
auth.subject = "email-address-to-impersonate";
const { people, contactGroups } = google.people({
version: "v1",
auth: auth
})
Reference:
Google Auth Library: Node.js Client
My company has made a custom photo-field in Sharepoint for it's news. I'm trying to use Microsoft Graph to fetch the images, but with no success.
This is the columns description:
{
"columnGroup": "Page Layout Columns",
"description": "",
"displayName": "Thumbnail image",
"enforceUniqueValues": false,
"hidden": false,
"id": "XXXXXXX-XXXX-XXXX-XXX-XXXXXXXXXXXX",
"indexed": false,
"name": "PublishingPageImage",
"readOnly": false,
"required": false
},
In the documentation for Microsoft Graph it is written that you can make a request like this
GET https://graph.microsoft.com/beta/sites/{site-id}/lists/{list-id}/items?expand=fields(select=Column1,Column2)
Although - no matter how I seem to write the request, i can't get the image field.
My most recent try has been this request:
https://graph.microsoft.com/beta/sites/knowit.sharepoint.com/lists/posts/items?expand=fields(select=PublishingPageImage)
The respons I got from Microsoft was this:
{
"error": {
"code": "-1, Microsoft.SharePoint.Client.ClientServiceException",
"message": "Cannot serialize data for type Microsoft.SharePoint.Publishing.Fields.ImageFieldValue.",
"innerError": {
"request-id": "f25e4851-0c1b-4061-ad6a-948d38004046",
"date": "2018-09-17T14:03:01"
}
}
}
Should I use something like .value or .data or .ImageUrl after the request? If i get a link or a data-value doesn't really matter. In the call for /me/ for Microsoft users there is a $value property for getting the user profile photo. Is it something like this?
It depends how image is stored.
If it is attachment you can do following:
string baseURL = $"https://{spTenant}/_api/web/lists('{ AA.config.listID}')/items({ siteData.Id})/AttachmentFiles";
string fileName = await GetFileNameAsync(baseURL);
string getPictureReqUrl = $"{baseURL}('{fileName}')/$value";
Stream responseStream = await GetPictureAsync(getPictureReqUrl);
private static async Task<Stream> GetPictureAsync(string reqUrl)
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", SPOToken);
HttpResponseMessage response = await client.GetAsync(reqUrl);
return await response.Content.ReadAsStreamAsync();
}
Important! This is not supported by graph yet, but you can use SharePoint Rest API
If picture is stored in document library you need to use Drive object instead
https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/resources/drive