Calling Microsoft Graph from Power Automate (and other daemon apps) with delegated permissions

Calling Microsoft Graph from Power Automate (and other daemon apps) with delegated permissions

Whenever you have some kind of an automated background process that needs to perform tasks via Microsoft Graph without logged-in user identity, you should always configure your app to use application permissions rather than delegated. However, there are quite a few Graph operations (e.g., those related to Planner) that only support delegated permissions, which is why sometimes we need to set up our background processes to run with a service account.

There are two ways of achieving this in Azure Logic Apps and Power Automate:

  • Preferably you should always create a a custom connector for situations like this. The benefit of a custom connector is that the service account can have multifactor authentication (MFA) enabled, and there’s no need to set up Azure Key Vault for storing the password or client secret. To sum it up, this method is more secure.
  • However, if for some reason you can’t create a custom connector, you can implement the OAuth resource owner credentials flow directly in your daemon application — whether your application is an Azure Logic App, a Power Automate flow, an Azure function or some other background process.

Because Microsoft has already provided us with a great tutorial for creating custom connectors, I won’t go through the steps here. Instead, I’ll focus on the alternative.

Table of Contents

  1. Gather the required information for authentication
  2. Authenticate to Microsoft Graph using the OAuth 2.0 password flow
  3. Parse the authorization response to obtain the access token
  4. Include the access token in the request when making calls to Microsoft Graph

Gather the required information for authentication

To get authorized to call Microsoft Graph, we’ll need the following pieces of information:

  • Tenant ID
  • Client ID
  • Client secret
  • Username
  • Password

The username and password should be a set of service credentials that have permissions to the resources you are planning to manage via Microsoft Graph. If you are planning on managing Teams via Graph, the service account also needs to have a Teams license.

The tenant ID, client ID and client secret you can obtain from the application registration you are using for authentication. If you haven’t set up an application registration for your flow in Azure AD yet, feel free to check out another one of my blog posts for instructions.

Both the user account password and the client secret are considered sensitive information. If they were leaked, they’d allow a potential attacker to perform tasks (within the scope of granted permissions) on our tenant via Microsoft Graph. Because of this, we need to store them in a secure place, namely Azure Key Vault. You should utilize an Azure Key Vault also in other types of daemon apps, such as Azure functions.

There is an Azure Key Vault connector in both Azure Logic Apps and Power Automate. The Get Secret action allows us to retrieve secret values from the specified key vault. To be able to do that, the Azure Logic Apps managed identity or the Power Automate flow author need to be granted “Get Secret” permissions to the vault. After that, you can get secrets from the vault simply by providing their name in the action.

Note that by default the secret value fetched from Azure Key Vault will show on the run logs which is a security issue. To prevent that from happening, we need to adjust the Get secret action settings, and turn on securing inputs and outputs. When we do this, the secret value won’t be visible on the run logs. It won’t show in any of the actions where it is used. Great!

We also need to restrict the edit permissions of the flow to the minimum. If we allow a lot of people to edit the flow, someone could edit the Get secret action settings to display the fetched values on the run log again, and this way discover the sensitive information we are attempting to hide.

After fetching the top-secret information from the key vault, I tend to put all of the pieces of information listed above into variables. This way it is easy for me to update them if they are changed or if I move the Power Automate flow to another tenant.

When the secret values are also in variables, it is faster to add them into actions. As I mentioned before, the secret values won’t show on the run log in relation to any of the actions where they are used, including these variables. The secret values are always hidden once the secure inputs and outputs options are turned on.

Authenticate to Microsoft Graph using the OAuth 2.0 password flow

Now that we’ve gathered all the information required for authentication, let’s do just that. What I am showing you here is in no way Azure Logic Apps or Microsoft Power Automate specific. We authenticate against Azure AD using OAuth 2.0 password flow (a.k.a. resource owner credentials flow) with a simple REST request in order to obtain an access token for Microsoft Graph. You can use this same method, e.g., in a PowerShell script or a C# daemon application because the only requirement for this is that you are able to make an HTTP request. Basically in any situation in which you need to use delegated permissions but are unable to have user interaction.

What we do here is call the token endpoint of our tenant’s Azure AD which will provide us an access token for Microsoft Graph (the resource) in exchange for the information contained in the request body.

Before we construct the request, we need to URL encode the client secret. You should also do the same for the password if contains special characters (which it should).

encodeUriComponent(variables('ClientSecret'))

After that, let’s configure an HTTP action with the following information:

The HTTP method POST
The request URL https://login.microsoftonline.com/your tenant id/oauth2/token
The Content-Type header application/x-www-form-urlencoded
The request body grant_type=password&resource=https://graph.microsoft.com&client_id=your client id&username=service account username&password=the URL encoded service account password&client_secret=the URL encoded client secret

Parse the authorization response to obtain the access token

When the above HTTP request is made, we get authenticated, and in the response, we’ll receive the access token for calling Microsoft Graph — amongst other pieces of information. Before we can use the access token, we need to parse the JSON in the response body to make the token available to us in the dynamic content panel.

Copy paste this schema to the action:

{
    "type": "object",
    "properties": {
        "token_type": {
            "type": "string"
        },
        "expires_in": {
            "type": "string"
        },
        "ext_expires_in": {
            "type": "string"
        },
        "expires_on": {
            "type": "string"
        },
        "not_before": {
            "type": "string"
        },
        "resource": {
            "type": "string"
        },
        "access_token": {
            "type": "string"
        }
    }
}

Include the access token in the request when making calls to Microsoft Graph

Now, the access token we just extracted doesn’t quite work on its own. We need to add the text “Bearer ” (notice the whitespace at the end) in front of the access token.

To make our lives a little bit easier, let’s create a new variable where we do just that. In the future, we can then just reference the variable without having to remember to type “Bearer ” to every request. Alternatively, you can use the Compose action to achieve the same thing; they are in fact a bit more performant than variables (a good thing to remember if your flows/apps are ever taking a long time to run).

There are actually two ways of how you can include the bearer token in the request.

1) Include the Authorization header.

2) Use the Raw authentication method.

Either one works just as well, so it really comes down to personal preference. Using the Raw dropdown value saves you from potential typos, but if you make a lot of HTTP requests on other platforms too, it might feel more natural to use the Authorization header.

Afterword

As I mentioned in the beginning, this is not the only way of authenticating to Microsoft Graph from Azure Logic Apps or Microsoft Power Automate using delegated permissions. You should always opt for creating a custom connector if at all possible. In that scenario, you’ll authenticate the connector when building the flow. It requires a bit more effort to set up but it allows the service account to use MFA, and you don’t need to set up an Azure key vault for storing the sensitive information.

Hopefully, you enjoyed reading this article, and if you’d like to hear from me again, the best way to ensure that is to follow me on Twitter. That’s the place where I’ll announce my new blog articles, share some tips and tricks and sometimes also offer glimpses to other things that I’m up to.

Thank you for reading and until next time!

Laura



48 thoughts on “Calling Microsoft Graph from Power Automate (and other daemon apps) with delegated permissions”

  • Hey Laura, thank you for this blog post. I’m trying to create a background service to create Planner tasks, like a lot of people here are trying to do, which requires this exact scenario.
    I’m re-creating the HTTP request inside my service in the way you described, but the response I’m getting is saying my user (that I passed) is not part of the directory. My service account user is definitely part of the Azure Active Directory.

    I’m guessing this response is not exactly as it seems and there might be more to it. Is there a missing configuration you might suggest I look at?

    public static bool DelegatedToken(string _host, string _domain, string _clientId, string _userName, string _password, string _clientSecret, string _tenantID, out string token)
    {

    token = null;

    var client = new RestClient($”{_host }{_domain}”);
    var request = new RestRequest(“/oauth2/token”, Method.POST);

    var encClientSec = HttpUtility.UrlEncode(_clientSecret);
    request.AddHeader(“Content-Type”, “application/x-www-form-urlencoded”);
    request.AddParameter(“application/x-www-form-urlencoded”, $”grant_type=password&resource=https://graph.microsoft.com&client_id={_clientId}&username={_userName}&password={_password}&client_secret={encClientSec}”, ParameterType.RequestBody);

    IRestResponse response = client.Execute(request);
    }

    • Hi Aaron, did you find a solution for that?
      I’m trying reach the same goal of creating\updating planner tasks here.
      For now I have that feeling that a custom connector could help.

  • Hi, very good Blog! thanks.
    Is the Username and Password the Office 365 Username and Password?

    Kind regards

  • Hey this is great.

    I am getting the following error when trying to get my token. I have inspected my flow and the URI and Body both look correct. Grant Type for sure is not missing.

    {“error”:”invalid_request”,”error_description”:”AADSTS900144: The request body must contain the following parameter: ‘grant_type’.\r\nTrace ID: 9734ccd5-5180-4f81-9432-ae61ef7c1f00\r\nCorrelation ID: 9e0c4293-5cd3-4e75-8a0d-66ed15d5ac48\r\nTimestamp: 2020-04-07 07:13:11Z”,”error_codes”:[900144],”timestamp”:”2020-04-07 07:13:11Z”,”trace_id”:”9734ccd5-5180-4f81-9432-ae61ef7c1f00″,”correlation_id”:”9e0c4293-5cd3-4e75-8a0d-66ed15d5ac48″,”error_uri”:”https://login.microsoftonline.com/error?code=900144″}

    • Hi Chris, did you encode the value for your client secret correctly? Check again your request body; the shown error looks like something was malformed… Good luck!

  • Fantastic article, I’m just getting started with Flow and the Graph API and this was exactly what I needed. One thing I did notice when I started working with the OneNote APIs was that for whatever reason the Delegated Permissions in the App Registration – Notes.Create, Notes.Read etc wouldn’t work. Ultimately, based on this article I went digging in the to MS documentation regarding Resource Owner grants and noticed a scope parameter where these permissions could be added. Once added the returned Token was able to perform the actions needed. For additional clarity I needed to also work with the Planner APIs so using Application permissions wasn’t an option but the App Registration DID work as expected in that scenario. Any idea why?

  • Hello Laura,
    thank you for your yery useful blog posts.
    I have a problem getting the accesstoken for delegated access in our backend powershell script. Login with basic auth is forbidden on our Tenant, so getting the accesstoken via password credential fails. Using client credentials cannot be used, because the script is running in the background without user interaction. Using an application accesstoken works fine, but with this token we cannot create new planner in teams.
    Thanks for some hints,
    Best regards,
    Lutz

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.