Azure's Graph api passwordprofile NULL for b2c users - microsoft-graph-api

Users sign up/login via Azure AD B2C using Identity provider Local Account-Email.
I can see users signed up (with their password) for the tenant:
When I run example "Manage User Accounts with Graph API" to check for local identity passwordProfiles they show null. My assumption is this property is automatically populated when a user creates the password same as other User resources.
Can someone give me some guidance what I'm missing?
public static async Task GetUserByIssuerAssignedID(AppSettings config, GraphServiceClient graphClient)
{
Console.Write("Enter user sign-in name (username or email address): ");
string userName = Console.ReadLine();
Console.WriteLine($"Looking for user with sign-in name '{userName}'...");
try
{
// Get user by sign-in name
var result = await graphClient.Users
.Request()
.Filter($"identities/any(c:c/issuerAssignedId eq '{userName}' and c/issuer eq '{config.TenantId}')")
.Select(e => new
{
e.PasswordProfile,
e.DisplayName,
e.Id,
e.Identities
})
.GetAsync();
if (result != null)
{
Console.WriteLine(JsonConvert.SerializeObject(result));
}
Thank you for your help

It is an expected result.
Azure AD B2C doesn't require the local identity users to change password next sign in. As the document says:
The property must set to .forceChangePasswordNextSignIn false.
Set forceChangePasswordNextSignIn as true is meaningless. In this case, passwordProfile won't be visible through GET method of Microsoft Graph API.
You can quickly verify it in Microsoft Graph Explorer.
For example, if you create a user with "forceChangePasswordNextSignIn": true in an Azure AD tenant, you will get passwordProfile in the result.
If you create a user with "forceChangePasswordNextSignIn": true in an Azure AD B2C tenant, you can get "passwordProfile" in the result but the password is null.
"passwordProfile": {
"password": null,
"forceChangePasswordNextSignIn": true,
"forceChangePasswordNextSignInWithMfa": false
}
We can never get user password using Microsoft Graph API or any other official API. Azure AD won't store password. So you can't get it.

Related

Skip offline access permission in Microsoft OIDC authorization

I'm using this code
var app = ConfidentialClientApplicationBuilder.Create(AzureAdApplicationId)
.WithTenantId("organizations")
.WithRedirectUri(AzureAdRedirectUrl)
.WithClientSecret(AzureAdSecretKey)
.Build();
azureAdScopes = new List<string>() { "email" };
var signInRequest = app.GetAuthorizationRequestUrl(azureAdScopes);
var uri = await signInRequest.ExecuteAsync();
which produces the url
https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?scope=email+openid+profile+offline_access&...
All I need is the user's username and I don't need offline access to the user's account. How can I remove them from the scope?
You could request the url without offline_access, but Azure AD v2.0 OAuth2 Account Consent Page automatically lists "Access your data anytime" even though offline_access is not specified in scope. This is an issue related.
The Note shows in the document:
At this time, the offline_access ("Maintain access to data you have
given it access to") and user.read ("Sign you in and read your
profile") permissions are automatically included in the initial
consent to an application.

Azure AD get Information from Claim

I use authentication with Azure AD B2C.
var Claims = User.Claims;
var ClientIdClaim = Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
gives me the Object-ID back. But I need also the user name. How I can access the username (marked yellow)?
You need to check if the claim is returned to the application. Go to azure portal->your user flow policy->Application claims
Also, you can test your policy using the "Run Now" link from the Azure portal(just like #Jas Suri suggested).
You will be redirected to https://jwt.ms/.

Is there a way to link the Google Identity Token to my company user in the database?

I'm setting up an action on google project which uses the OAuth & Google Sign In Linking Type.
Previously, I was using the userId that was sent in every request to look up the user in the database to see if there were accesstokens and refreshtokens available. But since userId is deprecated, I am looking for an alternative.
The user starts his/her dialog and then bumps into this piece of code:
app.intent('Give Color', async (conv, { color }) => {
conv.data[Fields.COLOR] = color;
if (conv.user.ref) {
await conv.user.ref.set({ [Fields.COLOR]: color });
conv.close(`I got ${color} as your favorite color.`);
return conv.close('Since you are signed in, I\'ll remember it next time.');
}
return conv.ask(new SignIn(`To save ${color} as your favorite color for next time`));
});
The "To continue, link Test App to your Google Account" on which the user selects the correct Google account.Then my /token endpoint is called on the OAuth server containing the Google ID Token (assertion) which holds all of the users data. I decode it, check in the database if the "sub" is already present, and I throw the following exception:
return res.status(401).send({ error: 'user_not_found' });
Then the normal OAuth procedure kicks in, where I deliver a token to Google. Sidenote: this is my own OAuth Server written in NodeJS. I am sure that the access- and refreshtoken are delivered to Google.
After token delivery, I get a new request on my action:
app.intent('Get Sign In', async (conv, params, signin) => {
if (signin.status !== 'OK') {
return conv.close('Let\'s try again next time.');
}
const color = conv.data[Fields.COLOR];
await conv.user.ref.set({ [Fields.COLOR]: color });
return conv.close(`I saved ${color} as your favorite color. `
+ 'Since you are signed in, I\'ll remember it next time.');
});
The signin.status has a value of "OK". But shouldn't the conv.user object contain the Google ID Token so that I can store the access- and refreshtoken along with this "sub" from the Google ID Token in my database? Or am I getting something wrong?
The content of the conv.user looks like this:
User {raw: Object, storage: Object, _id: undefined, locale: "en-BE", verification: "VERIFIED", …}
_id: undefined
[[StableObjectId]]: 7
access: Access {token: "ACCT-ATlbRmcpMI545WJFssRSlK1Jcza46NIB"}
entitlements: Array(0) []
id: undefined
last: Last {seen: Thu Aug 08 2019 10:53:17 GMT+0200 (Central Europea…}
locale: "en-BE"
name: Name {display: undefined, family: undefined, given: undefined}
permissions: Array(0) []
profile: Profile {token: undefined}
raw: Object {accessToken: "ACCT-ATlbRmcpMI545WJFssRSlK1Jcza46NIB", locale: "en-BE", lastSeen: "2019-08-08T08:53:17Z", …}
storage: Object {}
verification: "VERIFIED"
__proto__: Object {constructor: , _serialize: , _verifyProfile: , …}
conv.user.id is *DEPRECATED*: Use conv.user.storage to store data instead
It won't contain the Google ID of the user, because the user hasn't authorized that.
What they have authorized is whatever you've asked them to authorize via your OAuth server.
So you'll see the access token that your server has sent to the Assistant in conv.user.access, and you can then use this token to lookup who the user is in your database and take action accordingly.
If you specifically want their Google ID, you'll need to make sure that they use Google Sign-In on the same project as your Action (either through voice, a mobile app, or a webapp).
If you just need an ID so you can see when this user returns later, you can use the Google ID you get from Google Sign-In, or just generate an ID and store this in conv.user.storage.
Since I just want to have an ID, I will be using this:
If you just need an ID so you can see when this user returns later, you can use the Google ID you get from Google Sign-In, or just generate an ID and store this in conv.user.storage.
Thanks!

Microsoft Graph API - Insufficient privileges to complete the operation

I am trying to use Microsoft Graph API to update another user in Active Directory.
I have the following permissions set for both user and application at https://apps.dev.microsoft.com/
I've requested the following scopes:
Directory.ReadWrite.All
User.ReadWrite.All
Group.ReadWrite.All
I am able to get a listing of all users in the directory, but when trying to update (in this case, the city) it fails:
GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient ();
var usersResponse = graphClient.Users.Request ().GetAsync ();
var users = usersResponse.Result;
// hard coding user id for now
var userId = "9a5b83cd-85ff-4ad1-ab2f-b443941a518e";
var user = users.FirstOrDefault (m => m.Id == userId);
if (user != null) {
user.City = "New York";
await graphClient.Me.Request ().UpdateAsync (user);
}
I get:
{
Code : Authorization_RequestDenied
Message : Insufficient privileges to complete the operation.
Inner error
}
The user I am logged in as is a Global Administrator of the directory.
I took the JWT token, headed over to https://jwt.io and these are the roles I am seeing:
Directory.Read.All
Directory.ReadWrite.All
Files.ReadWrite
Group.ReadWrite.All
Mail.Send
User.Read
User.Read.All
User.ReadWrite.All
Do I need other permissions to make this happen?
At the end of the day, I'd like to create a console app (not web app) that I can update other user information in the directory. But I figured using this sample app provided by Microsoft is a good start.
The reason you're seeing this is because you're passing the complete user object rather than only the city property. In other words, you're attempting to update every property in that user record, including several that are read-only.
This is one of those cases where having an SDK that wraps a REST API can be result in some confusing errors. As REST API, it is stateless so passing in the entire user property set is telling the API you want to PATCH all of those values.
You're also passing in a different user object into the me object (i.e. you're replacing all of your property values with this other user's property values):
await graphClient.Me.Request().UpdateAsync(user);
Instead, try this:
GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient ();
// hard coding user id for now
var userId = "9a5b83cd-85ff-4ad1-ab2f-b443941a518e";
await graphClient.Users[userId].Request ().UpdateAsync(new User
{
City = "New York"
});

Google Fusion Tables 400 invalid_grant

I am trying to use google fusion tables API from Java. I am getting 400 Bad Request and
"error" : "invalid_grant".
I am using Service account API and OAuth2.0 to authorize my requests.
1) I have downloaded my secret key and use the below snippet for Credential.
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId("[[293873940759-je0f8o4j90nmqvlbl6ofbtm0492paikd#developer.gserviceaccount.com]]")
.setServiceAccountPrivateKeyFromP12File(KeyResources.INSTANCE.getFileFromPath("b9b48154142d235bbd711c3eb8f86bb2ec155faf-privatekey.p12"))
.setServiceAccountScopes("oauth2:https://www.googleapis.com/auth/fusiontables")
.build();
2) I am using the below code to connect to code snippet.
Credential credential = ServiceAccount.INSTANCE.getCredentials();
Fusiontables fusiontables = new Fusiontables.Builder(ServiceAccount.HTTP_TRANSPORT, ServiceAccount.JSON_FACTORY, credential)
.setApplicationName("sfdgdfgdfgdfgdfgdfgf345q23")
.setHttpRequestInitializer(credential)
.build();
Fusiontables.Table.List listTables = fusiontables.table().list();
This is the error i am getting.
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "invalid_grant"
}
at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:103)
at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:303)
at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:323)
at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:340)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:508)
Any help would be great.
Thanks,
Ganesh V
I think you have to remove the "[]" around your service account Id.
Apart from that, if you get 403 unauthorized error after you change the service account id, it might because that you have not grant the edit access for your table to you service account. To change the access, simply share you fusion table to you service account email and grant the "can edit" access.
Hope it help you.
I think you need to set a user email, as the service account need to delegate a user account:
GoogleCredential credential = new GoogleCredential.Builder()
...
.setServiceAccountUser(UserEmail)
and make sure the service Fusion Tables API is on, and in the user's owner domain console grant the correct scope to your service account:
https://www.googleapis.com/auth/fusiontables

Resources