All I am trying to do is to fetch emails for a userID which is accessible to other users without having them logging into their Microsoft accounts. I have looked at numerous SO posts (this), code samples (this, this) and looked into the specs of OpenID and other docs (this), but still not able to figure it out.
I have registered app in azure portal and granted required permissions. Using the sample app I am able to fetch user list, but not the email list. I compared the request headers for both user query and email query. Both look the same. Can someone please tell me what I am doing wrong?
Code is given below:
Startup.Auth.cs
public static string clientId = "<CLIENT ID>";
public static string clientSecret = <CLIENT SECRET>;
private static string authority = "https://login.microsoftonline.com/common/v2.0";
public static string redirectUri = "https://localhost:44316/";
private void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
//Scope = "openid profile offline_access Mail.Read",
Scope = "email profile openid offline_access User.Read Mail.Read",
//ResponseType = "id_token",
ResponseType = "code id_token",
TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, NameClaimType = "name" },
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = this.OnAuthenticationFailedAsync,
SecurityTokenValidated = this.OnSecurityTokenValidatedAsync
}
});
}
SyncController.cs
private const string AuthorityFormat = "https://login.microsoftonline.com/{0}/v2.0";
private const string MSGraphScope = "https://graph.microsoft.com/.default";
//private const string MSGraphQuery = "https://graph.microsoft.com/v1.0/users";
//private const string MSGraphQuery = "https://graph.microsoft.com/v1.0/me";
private const string MSGraphQuery = "https://graph.microsoft.com/v1.0/me/messages";
public async Task GetAsync(string tenantId)
{
MSALCache appTokenCache = new MSALCache(Startup.clientId);
// Get a token for the Microsoft Graph. If this line throws an exception for
// any reason, we'll just let the exception be returned as a 500 response
// to the caller, and show a generic error message to the user.
ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(Startup.clientId, string.Format(AuthorityFormat, tenantId), Startup.redirectUri,
new ClientCredential(Startup.clientSecret), null, appTokenCache.GetMsalCacheInstance());
AuthenticationResult authResult = await daemonClient.AcquireTokenForClientAsync(new[] { MSGraphScope });
HttpClient client = new HttpClient();
// Uses SendAsync
/*HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, MSGraphQuery);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
//request.Headers.Add("client-request-id", System.Guid.NewGuid().ToString());
//request.Headers.Add("return-client-request-id", "true");
HttpResponseMessage response = await client.SendAsync(request);*/
// Uses GetAsync
client.BaseAddress = new System.Uri(MSGraphQuery);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + authResult.AccessToken);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync(MSGraphQuery);
}
Edit 1: Here are the request and response values for user list (working) and email list (not working):
Request body to fetch user list (working):
{Method: GET, RequestUri: 'https://graph.microsoft.com/v1.0/users', Version: 1.1, Content: <null>, Headers:
{
Accept: application/json
Authorization: Bearer eyJ0eXAiOiJKV...
}}
Response when fetching user list (working):
{StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Transfer-Encoding: chunked
request-id: 02034f96-f519-4f5f-b47d-efb98dff0072
client-request-id: 02034f96-f519-4f5f-b47d-efb98dff0072
x-ms-ags-diagnostic: {"ServerInfo":{"DataCenter":"South India","Slice":"SliceC","Ring":"5","ScaleUnit":"002","Host":"AGSFE_IN_8","ADSiteName":"INS"}}
OData-Version: 4.0
Duration: 76.0712
Strict-Transport-Security: max-age=31536000
Cache-Control: private
Date: Fri, 02 Nov 2018 12:36:51 GMT
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true; IEEE754Compatible=false; charset=utf-8
}}
Request body to fetch emails (not working):
{Method: GET, RequestUri: 'https://graph.microsoft.com/v1.0/me/mailFolders('Inbox')/messages', Version: 1.1, Content: <null>, Headers:
{
Accept: application/json
Authorization: Bearer eyJ0eXAiOiJKV...
}}
Response when fetching list of emails (not working):
{StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Transfer-Encoding: chunked
request-id: 5b728866-b132-404f-9986-70fc56e57c3c
client-request-id: 5b728866-b132-404f-9986-70fc56e57c3c
x-ms-ags-diagnostic: {"ServerInfo":{"DataCenter":"South India","Slice":"SliceC","Ring":"5","ScaleUnit":"002","Host":"AGSFE_IN_6","ADSiteName":"INS"}}
Duration: 4205.8126
Strict-Transport-Security: max-age=31536000
Cache-Control: private
Date: Fri, 02 Nov 2018 12:43:03 GMT
Content-Type: application/json
}}
You're using Client_Credentials
to authenticate the app and using the /me
path in your REST call. These two do not work together.
Behind the scenes /me
is translated into the currently authenticated user (i.e. /users/user@domain
. Since you don't have a user authenticated, it simply isn't possible for the Graph to translate your request into an actionable call.
You need to explicitly reference the user using either their id
or their userPrincipalName
:
/v1.0/users/123e4567-e89b-12d3-a456-426655440000/mailFolders('Inbox')/messages
/v1.0/users/[email protected]/mailFolders('Inbox')/messages