Why do users have different ID formats between the Microsoft Graph API and UserIDs in bot framework? - node.js

I have used Graph Service to get user information by email. Here is my sample code:
var user = null;
const GraphService = require('graph-service');
const ClientCredentials = require('client-credentials');
const tenant = 'my-company.com';
const clientId = '0b13aa29-ca6b-42e8-a083-89e5bccdf141';
const clientSecret = 'lsl2isRe99Flsj32elwe89234ljhasd8239jsad2sl=';
const credentials = new ClientCredentials(tenant, clientId, clientSecret);
const service = new GraphService(credentials);
service.get('/users/tnguyen482#my-company.com').then(response => {
user = response.data;
});
This would return user which has ID = 9422e847-0000-1111-2222-d39d550a4fb6
But when I use Botbuilder-teams to get fetch members, the user information return from which has different format of ID. Here is my sample code:
var user = null;
var teams = require("botbuilder-teams");
var connector = new teams.TeamsChatConnector({
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
});
let conversationId = session.message.address.conversation.id;
var userEmail = "tnguyen482#my-company.com";
connector.connector.fetchMembers(
"https://smba.trafficmanager.net/amer-client-ss.msg/",
conversationId,
(err, result) => {
if (err) {
console.log('Cannot get member of current conversation');
}
else {
if (result.length > 0){
result.forEach(function(item) {
if (item.email == userEmail){
user = item;
}
});
}
}
}
);
This would return user which has ID = 29:1zJXjlM7ifjqawGVxXx_4xxx56BFCCIJWfPbWrVDSdxsKUhi9IXyXXYNLOKCLHodN7WgEzz31lBKcZwtWvMzoUw
My question is why on the same user with different ways approach to retrieve data return different ID format?
Besides, my purpose is that I will use the user ID in address for botbuilder to send personal message to user.

User ID is not defined the same within the Graphs Service as it is within Botbuilder. The botbuilder userID is a key for that given user as connected to the conversation within the bot (and is only relevant within the context of the conversation with the bot), and the userID within Graph Service is a unique identity key for a user of Azure AD.
These are not the same API or part of a universal connector, so these IDs do not cross over to one another. Many people create some sort of dictionary of users so that the 2 can be looked up and used accordingly in their application.

Related

How to properly get Twitter screenName from firebase auth?

Following these links https://firebase.google.com/docs/auth/web/manage-users#update_a_users_profile and https://firebase.google.com/docs/auth/web/manage-users#get_a_users_provider-specific_profile_information
I was able to authenticate user and log in with their twitter account. However, I want to get the screenName of an user. How will I do that?
I've checked some network request and I see the screenName attribute. I've checked onAuthStateChanged and it returns user attribute but without the screenName.
I needed to do this from Node, however, Google/Firebase Auth does not store a user's Twitter handle (at least it's not accessible through firebase-admin).
However, they do make the Twitter uid accessible as the question points out. With that, you can subsequently call Twitter's API to get a user by their uid and the result will return the handle a.k.a username:
import { TwitterApi } from 'twitter-api-v2';
import { auth } from 'firebase-admin';
const BEARER = process.env['TWITTER_BEARER_TOKEN'] as string;
const logTwitterHandleFromUid = async (googleUid: string): Promise<void> => {
// get Google Auth information associated with the target user
const { providerData } = await auth().getUser(googleUid);
// pull out the Twitter information
const twitter = providerData.find((p) => p.providerId.includes('twitter'));
if (!twitter) {
console.error('User does not have a linked Twitter account')
process.exit(1)
}
const details = await new TwitterApi(BEARER).v2.user(twitter.uid);
// pull out the Twitter handle
const twitter_handle = details.data.username;
return console.log(twitter_handle);
};
See Get a user's profile documentation. The .displayName property should have it.
var user = firebase.auth().currentUser;
var name, email, photoUrl, uid, emailVerified;
if (user != null) {
name = user.displayName;
email = user.email;
photoUrl = user.photoURL;
emailVerified = user.emailVerified;
uid = user.uid; // The user's ID, unique to the Firebase project. Do NOT use
// this value to authenticate with your backend server, if
// you have one. Use User.getToken() instead.
}

How to get groups that user belong to?

Using Graph Service, I have tried to get list of groups that user belong to. Here is my sample code:
var userMemberOf = null;
var userMemberGroups = null;
const GraphService = require('graph-service');
const ClientCredentials = require('client-credentials');
const tenant = 'my-company.com';
const clientId = '0b13aa29-ca6b-42e8-a083-89e5bccdf141';
const clientSecret = 'lsl2isRe99Flsj32elwe89234ljhasd8239jsad2sl=';
const credentials = new ClientCredentials(tenant, clientId, clientSecret);
const service = new GraphService(credentials);
service.get('/users/tnguyen482#my-company.com/memberOf').then(response => {
userMemberOf = response.data;
});
var settings = {
"securityEnabledOnly": true
}
service.post('/users/tnguyen482#my-company.com/getMemberGroups', settings).then(response => {
userMemberGroups = response.data;
});
The data return from both get & post method was an empty list. I have tried another user id but the result is the same. Am I correct when using method memberOf and getMemberGroups to get list of groups that user belong to? Does my sample code correct?
When the user is in no group graph will return an empty result.
Otherwise, when an error occured (e.g. accessDenied or user not found) an corresponding http-status with an errormessage will be returned (more information in the documentation).
The operations you are using seem to be correct.
If you want to rule out that your operations/code is incorrect, you should try executing the operations in the Graph Explorer.
Its a great tool for debugging and you can even login for access to your own data.

Get AccountName / UPN in a UWP App when logged on in Azure

I am creating a UWP app which shows certain data, depending on the logged on user.
The user is logged on in Windows Azure and the computer account is also joined to Azure.
I have enabled the "Account Information" feature in the app manifest.
I am trying to find out the user data, using the User Class, like mentioned in several examples online:
private async void GetAllUserData()
{
var users = await User.FindAllAsync();
foreach (var user in users)
{
var authenticationStatus = user.AuthenticationStatus;
var nonRoamableId = user.NonRoamableId;
var provider = await user.GetPropertyAsync(KnownUserProperties.ProviderName);
var accountName = await user.GetPropertyAsync(KnownUserProperties.AccountName);
var displayName = await user.GetPropertyAsync(KnownUserProperties.DisplayName);
var domainName = await user.GetPropertyAsync(KnownUserProperties.DomainName);
var principalName = await user.GetPropertyAsync(KnownUserProperties.PrincipalName);
var firstName = await user.GetPropertyAsync(KnownUserProperties.FirstName);
var guestHost = await user.GetPropertyAsync(KnownUserProperties.GuestHost);
var lastName = await user.GetPropertyAsync(KnownUserProperties.LastName);
var sessionInitiationProtocolUri = await user.GetPropertyAsync(KnownUserProperties.SessionInitiationProtocolUri);
var userType = user.Type;
}
}
The only properties I can get from the user object are:
DisplayName
AuthenticationStatus
NonRoamableId
UserType
All other properties remain empty. From my understanding, when I am logged in to Windows Azure, at least the principal name should have a value.
What am I doing wrong - or in other words - what do I have to do, to get account information?
After enabling "Enterprise Authentication" feature in my app manifest, the UPN is filled in the principalName variable.
I know, this does not the real authentication job for the application, but for my purpose it is sufficient to have the UPN, authenticated in Windows.
For more information about adding Azure authentication to an app I have found the following links:
https://learn.microsoft.com/en-us/azure/app-service-mobile/app-service-mobile-windows-store-dotnet-get-started-users
https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-native-uwp-v2/

Getting the email from external providers Google and Facebook during account association step in a default MVC5 app

Apparently you can do this with the Facebook provider by adding scopes to the FacebookAuthenticationOptions object in Startup.Auth.cs:
http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the-vs-2013-project-templates.aspx
List<string> scope = new List<string>() { "email" };
var x = new FacebookAuthenticationOptions();
x.Scope.Add("email");
...
app.UseFacebookAuthentication(x);
How to do the same with Google provider? There isn't a x.Scope property for the GoogleAuthenticationOptions class/object!
PLEASE SEE UPDATES AT THE BOTTOM OF THIS POST!
The following works for me for Facebook:
StartupAuth.cs:
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = "x",
AppSecret = "y"
};
facebookAuthenticationOptions.Scope.Add("email");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
ExternalLoginCallback method:
var externalIdentity = HttpContext.GetOwinContext().Authentication.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;
And for Google:
StartupAuth.cs
app.UseGoogleAuthentication();
ExternalLoginCallback method (same as for facebook):
var externalIdentity = HttpContext.GetOwinContext().Authentication.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;
If I set a breakpoint here:
var email = emailClaim.Value;
I see the email address for both Facebook and Google in the debugger.
Update 1: The old answer had me confused so I updated it with the code I have in my own project that I just debugged and I know works.
Update 2: With the new ASP.NET Identity 2.0 RTM version you no longer need any of the code in this post. The proper way to get the email is by simply doing the following:
Startup.Auth.cs
app.UseFacebookAuthentication(
appId: "x",
appSecret: "y");
app.UseGoogleAuthentication();
AccountController.cs
//
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var result = await SignInHelper.ExternalSignIn(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresTwoFactorAuthentication:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
You need to explicitly configure the FacebookAuthenticationOptions to get the email address from the authenticated user.
In your MVC5 project, add these lines in the Startup.Auth.cs
var options = new FacebookAuthenticationOptions() {
AppId = "xxxxxxxx",
AppSecret = "xxxxxxxxx"
};
options.Scope.Add("email");
app.UseFacebookAuthentication(options);
Update
Reduced my sample code to the absolute minimum. Your updated code works fine by the way, I have also tried it with both Facebook and Google.
In ASP.NET Core Facebook authentication the Facebook middleware seems to no longer pass in the email, even if you add it to the scope. You can work around it by using Facebook's Graph Api to request the email.
You can use any Facebook Graph Api client or roll your own, and use it to invoke the Graph api as follows:
app.UseFacebookAuthentication(options =>
{
options.AppId = Configuration["Authentication:Facebook:AppId"];
options.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
options.Scope.Add("public_profile");
options.Scope.Add("email");
options.Events = new OAuthEvents
{
OnCreatingTicket = context => {
// Use the Facebook Graph Api to get the user's email address
// and add it to the email claim
var client = new FacebookClient(context.AccessToken);
dynamic info = client.Get("me", new { fields = "name,id,email" });
context.Identity.AddClaim(new Claim(ClaimTypes.Email, info.email));
return Task.FromResult(0);
}
};
});
You can find a more detailed example about how to use it here: http://zainrizvi.io/2016/03/24/create-site-with-facebook-login-using-asp.net-core/#getting-the-email-address-from-facebook

How to get and display the email using the Facebook provider in ASP.NET Identity and MVC 5

UPDATE: PLEASE SEE THIS POST: https://stackoverflow.com/a/20379623/687549
Been reading I think almost all questions on SO about external providers and how to get extra info/data/metadata/claims/orwhateveryouwannacallit in particular the email address which many use as the username on modern websites.
So the problem that I was facing was that I wanted to retrieve the email from the Facebook provider with as little code as possible. I thought to myself; the new ASP.NET Identity framework must have this buil-in and can do this with probably just a couple of lines of code. I searched and all I could find was these imensely large chunks of code and I thought to myself: there has got to be another more simpler way. And here it is, as an answer in this questionthread.
I managed to get this working with both Facebook and Google but what I'm concerned about is wheather or not I'm doing this right without any consequenses somewhere else in the code.
For instance: Do you really only need to specify the Scopes.Add("email") for it all to work or why haven't I been able to find more info about this on the interweb?
UPDATE: PLEASE SEE THIS POST: https://stackoverflow.com/a/20379623/687549
Startup.Auth.cs:
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = "myAppId",
AppSecret = "myAppSecret"
};
facebookAuthenticationOptions.Scope.Add("email");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
AccountController (default mvc 5 template app stuff)
//
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// These next three lines is how I get the email from the stuff that gets returned from the Facebook external provider
var externalIdentity = HttpContext.GetOwinContext().Authentication.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;
// Sign in the user with this external login provider if the user already has a login
var user = await UserManager.FindAsync(loginInfo.Login);
if (user != null)
{
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
else
{
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
// Populate the viewmodel with the email
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { UserName = email });
}
}
I have the same problem. You need to edit and add this code to ExternalLoginCallback in the AccountController
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// added the following lines
if (loginInfo.Login.LoginProvider == "Facebook")
{
var identity = AuthenticationManager.GetExternalIdentity(DefaultAuthenticationTypes.ExternalCookie);
var access_token = identity.FindFirstValue("FacebookAccessToken");
var fb = new FacebookClient(access_token);
dynamic myInfo = fb.Get("/me?fields=email"); // specify the email field
loginInfo.Email = myInfo.email;
}
Note the code dynamic myInfo = fb.Get("/me?fields=email"); this will work for facebook app with version 2.4, but for old version you can write this
dynamic myInfo = fb.Get("email");

Resources