Connect to Microsoft Graph from MVC on behalf of l

2019-07-26 22:33发布

问题:

I have created an MVC web app, that has certain pages that require a user to be logged in. The app is multitenant, and the authentication is configured in the Startup.Auth.cs. The ConfigureAuth file looks like this:

public void ConfigureAuth(IAppBuilder app){
    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

    app.UseCookieAuthentication(new CookieAuthenticationOptions());

    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions {
        ClientId = clientId,
        Authority = authority,
        RedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"],                
        TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters{
            ValidateIssuer = false,
        },
        Notifications = new OpenIdConnectAuthenticationNotifications(){
            SecurityTokenValidated = (context) => {
                return Task.FromResult(0);
            },
            AuthorizationCodeReceived = (context) => {
                var code = context.Code;

                ClientCredential credential = new ClientCredential(clientId, appKey);
                string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
                string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
                AuthenticationContext authContext = new AuthenticationContext(
                    aadInstance + tenantID,
                    new ADALTokenCache(signedInUserID)
                );
                AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                    code,
                    new Uri(ConfigurationManager.AppSettings["ida:RedirectUri"]),
                    credential,
                    graphResourceID
                );

                return Task.FromResult(0);
            },
            AuthenticationFailed = (context) => {
                context.HandleResponse(); // Suppress the exception
                context.Response.Redirect("/Error?message=" + context.Exception.Message);
                return Task.FromResult(0);
            }
        }
    });
}

This works - perfectly. My problem is, I would love to this the same authorization in my controller, when calling a Microsoft Graph endpoint.

I can see that the AccessToken contained in the AuthenticationResult has the correct scopes - meaning I should be able to reuse this when calling Graph, right?

But how do I use this in my controller? And how do I ensure the token is refreshed?

All examples I can find either use MSAL with v2 endpoint, or connects on behalf of the client - this does not work for me.

回答1:

You cannot reuse the token from within your controller and send it to graph so that graph thinks you are the user. This doesn't work, cause the token also contains the ip address of the client and that is not the ip address where your controller runs (well maybe only on your developer machine). Instead your application must have the Directory.AccessAsUser.All permission.

If you have this, within your backend you can create a

new AuthenticationContext("https://login.microsoftonline.com/common/")

and call AuthenticationContext.AcquireTokenAsync(scope, clientCredentials, userAssertation).

  • In this case the scope has to be https://graph.microsoft.com/.
  • clientCredentials must contain your application id and secret.
  • userAssertation must contain
    • the user token as assertation
    • the type should be urn:ietf:params:oauth:grant-type:jwt-bearer
    • the username must be the users UPN

When this call returns, you'll get a new token. This can be used in graph calls as bearer token from your controller code to access resources as the user itself would do it.

But ensure that this token doesn't leak out of your control (like the application secret). Cause as long as this token doesn't expire it can be used from any machine in the world to act as the user.

Update

If you don't have a user token, you need an application token to access the graph api. According to https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow you can get a token for your application by calling

using (var client = new HttpClient())
{
    var content = new Dictionary<string, string>
    {
        { "grant_type", "client_credentials" },
        { "scope", "https://graph.microsoft.com/.default"},
        { "client_id", "<ApplicationId>" },
        { "client_secret", "<ApplicationSecret>" }
    };

    var response = await client.PostAsync($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", content);
    var content = await response.Content.ReadAsStringAsync();
    dynamic answer = JObject.Parse(content);

    return answer.access_token;
}

When you send this token to graph in the authorization header with the prefix Bearer you should be able to access all resources within the given tenant, which have been granted to your application.