Reputation: 1388
I write a new application to access office data through the rest API, therefore i would like to use the new Authentication Model (V2.0 Endpoint)
What's different about the v2.0 endpoit
I can get a token with a call to
private static string[] scopes = { "https://outlook.office.com/mail.read", "https://outlook.office.com/calendars.read" };
public async Task<ActionResult> SignIn()
{
... SNIP
Uri authUri = await authContext.GetAuthorizationRequestUrlAsync(scopes, null, clientId, redirectUri, new UserIdentifier("contoso@foo", UserIdentifierType.RequiredDisplayableId), null);
return Redirect(authUri.ToString());
}
authContext.AcquireTokenByAuthorizationCodeAsync(authCode, redirectUri, credential, scopes)
The Problem is the second call to
public async Task<ActionResult> SignIn()
{
... SNIP
var authResult = authContext.AcquireTokenSilentAsync(scopes, clientId, new UserIdentifier("[email protected]", UserIdentifierType.RequiredDisplayableId))
}
The Returned token does contain the UniqueId, but this information is not stored in the Token object. The UserInfo of the Token is always null. Because this field is null, the Token cache cannot find the token.
Thank you for your Hints and Ideas
Returned Token
{
"aud":"https://outlook.office.com",
"iss":"https://sts.windows.net/f2ac6f3f-3df0-4068-a677-e4dfdf924b2/",
"iat":146 dfdf21,
"nbf":146 dfdf4621,
"exp":1463 dfdf38521,
"acr":"1",
"amr":[
"pwd"
],
"appid":"b13dfdf9-0561-4dfdff5-945c-778dfdf0de5cd",
"appidacr":"1",
"family_name":"Pan",
"given_name":"Peter",
"ipaddr":"12.12.12.17",
"name":"Peter Pan",
"oid":"4b83dfdfdb-f6db-433e-b70a-2f9a6dbbeb48",
"puid":"100dfdfdfF5FBC",
"scp":"Calendars.Read Mail.Read Mail.ReadWrite",
"sub":"Z-chdfdsfnWqduUkCGZpsIdp-fdhpMMqqtwcHGs",
"tid":"f2ac6f3f-3560-4068-a677-e4bfe0c924b2",
"unique_name":"foo@contoso",
"upn":"foo@contoso",
"ver":"1.0"
}
Similar question: Here
Upvotes: 3
Views: 3221
Reputation: 596
Microsoft has removed the profile_info as you can read here: Important Updates to ADV2
At the moment the library has a bug, because it still check it, and if it's null, it won't return the userinformations.
The correct informations are in token_id...
Class: TokenResponse
private AuthenticationResultEx GetResult(string token, string scope, long expiresIn)
{
DateTimeOffset expiresOn = (DateTimeOffset) (DateTime.UtcNow + TimeSpan.FromSeconds((double) expiresIn));
AuthenticationResult authenticationResult = new AuthenticationResult(this.TokenType, token, expiresOn);
ProfileInfo profileInfo = ProfileInfo.Parse(this.ProfileInfoString);
if (profileInfo != null)
{
string tenantId = profileInfo.TenantId;
string str1 = (string) null;
string str2 = (string) null;
if (!string.IsNullOrWhiteSpace(profileInfo.Subject))
str1 = profileInfo.Subject;
if (!string.IsNullOrWhiteSpace(profileInfo.PreferredUsername))
str2 = profileInfo.PreferredUsername;
authenticationResult.UpdateTenantAndUserInfo(tenantId, this.ProfileInfoString, new UserInfo()
{
UniqueId = str1,
DisplayableId = str2,
Name = profileInfo.Name,
Version = profileInfo.Version
});
}
return new AuthenticationResultEx()
{
Result = authenticationResult,
RefreshToken = this.RefreshToken,
ScopeInResponse = AdalStringHelper.CreateArrayFromSingleString(scope)
};
}
I hope they will fix it soon, I'm also waiting :-)
Edit:
I found something interesting here: Dev Outlook get started
As I already said, all informations stored in token_id, in the link above you can read:
The prerelease version of ADAL v4 doesn't return the ID token directly, but it is accessible. The method included here is intended to work around this issue until ADAL is updated.
They explain a way to access the Token:
private string GetUserEmail(AuthenticationContext context, string clientId)
{
// ADAL caches the ID token in its token cache by the client ID
foreach (TokenCacheItem item in context.TokenCache.ReadItems())
{
if (item.Scope.Contains(clientId))
{
return GetEmailFromIdToken(item.Token);
}
}
return string.Empty;
}
private string GetEmailFromIdToken(string token)
{
// JWT is made of three parts, separated by a '.'
// First part is the header
// Second part is the token
// Third part is the signature
string[] tokenParts = token.Split('.');
if (tokenParts.Length < 3)
{
// Invalid token, return empty
}
// Token content is in the second part, in urlsafe base64
string encodedToken = tokenParts[1];
// Convert from urlsafe and add padding if needed
int leftovers = encodedToken.Length % 4;
if (leftovers == 2)
{
encodedToken += "==";
}
else if (leftovers == 3)
{
encodedToken += "=";
}
encodedToken = encodedToken.Replace('-', '+').Replace('_', '/');
// Decode the string
var base64EncodedBytes = System.Convert.FromBase64String(encodedToken);
string decodedToken = System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
// Load the decoded JSON into a dynamic object
dynamic jwt = Newtonsoft.Json.JsonConvert.DeserializeObject(decodedToken);
// User's email is in the preferred_username field
return jwt.preferred_username;
}
I don't tested this yet, but I will update this post when I have tested it, or another one will please make a comment, if he's faster :-)
Upvotes: 3