Reputation: 391
I was trying to make a REST Api call to Microsoft graph to get my organization users. I was able to make a successful call using postman. However, I couldn't make the same successful call using c# code. While investigating using JWT decoder, the token I am getting is different from the one I get using postman. I must be missing something. I am using MVC 5 and .Net 4.6
public static async Task<AuthenticationResult> GetGraphAccessTokenAsync(string tenant, string clientId, string clientKey)
{
var authority = string.Format("https://login.microsoftonline.com/{0}", tenant);
var resource = "https://graph.microsoft.com";
AuthenticationContext authenticationContext = new AuthenticationContext(authority);
var clientCredential = new ClientCredential(clientId, clientKey);
var result = await authenticationContext.AcquireTokenAsync(resource, clientCredential);
return result;
}
Upvotes: 0
Views: 1777
Reputation: 9411
With different flows to get token ,the result will be different. For Authorization Code flow, it will get Delegated access token which contains upn and scope. For client credentials flow, it will get a basic(from application permissions) access token.
When you use Authorization Code flow: You will get access token and id token which also contains user's upn and scope like this:
{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/f62479de-8353-4507-aaf3-6a52320f641c/",
"iat": 1521565239,
"nbf": 1521565239,
"exp": 1521569139,
"app_displayname": "MicrosoftGraphClient",
"appid": "2024c60c-fe49-4ca0-80e8-94132f56d7c4",
"family_name": "Yang",
"given_name": "Wayne",
"name": "Wayne Yang",
"unique_name": "[email protected]",
...
"tid": "f62472de-8358-4507-aaf3-6a52320f641c",
}
When you use client credentials flow: You will get access token without usre's upn and scope like this:
{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/f62479de-8353-4507-aaf3-6a52320f641c/",
"iat": 1521555934,
"nbf": 1521555934,
"exp": 1521559834,
"app_displayname": "MicrosoftGraphClient",
"appid": "2024c60c-fe49-4ca0-80e8-94132f56d7c4",
"roles": [
"Directory.Read.All",
"User.Read.All",
...
"Mail.ReadWrite",
],
"tid": "f62471de-8358-4907-aaf3-6a52320f741c",
}
You can use Authorization code grant flow in your code. However, since you want to call microsoft graph API, I recommend you use MSAL with v2 endpoint , not ADAL. Because that if you use ADAL , it may cause some issues such as the credential cache being cleared..
For .NET 4.6 MVC app, you can refer to this sample. This sample use UseOpenIdConnectAuthentication
with Authorization code grant flow:
public partial class Startup
{
// The appId is used by the application to uniquely identify itself to Azure AD.
// The appSecret is the application's password.
// The redirectUri is where users are redirected after sign in and consent.
// The graphScopes are the Microsoft Graph permission scopes that are used by this sample: User.Read Mail.Send
private static string appId = ConfigurationManager.AppSettings["ida:AppId"];
private static string appSecret = ConfigurationManager.AppSettings["ida:AppSecret"];
private static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
private static string graphScopes = ConfigurationManager.AppSettings["ida:GraphScopes"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// The `Authority` represents the Microsoft v2.0 authentication and authorization service.
// The `Scope` describes the permissions that your app will need. See https://azure.microsoft.com/documentation/articles/active-directory-v2-scopes/
ClientId = appId,
Authority = "https://login.microsoftonline.com/common/v2.0",
PostLogoutRedirectUri = redirectUri,
RedirectUri = redirectUri,
Scope = "openid email profile offline_access " + graphScopes,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
// In a real application you would use IssuerValidator for additional checks,
// like making sure the user's organization has signed up for your app.
// IssuerValidator = (issuer, token, tvp) =>
// {
// if (MyCustomTenantValidation(issuer))
// return issuer;
// else
// throw new SecurityTokenInvalidIssuerException("Invalid issuer");
// },
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async (context) =>
{
var code = context.Code;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new SessionTokenCache(signedInUserID,
context.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(
appId,
redirectUri,
new ClientCredential(appSecret),
userTokenCache,
null);
string[] scopes = graphScopes.Split(new char[] { ' ' });
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, scopes);
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
}
Also, you can refer to this official documentation to achieve your scenario.
Upvotes: 1