getOffice365GroupsActivityDetail and S2SUnauthorized and custom connectors

It always impresses and scares me when I am trying to figure something out and while Googling I come across one of my own blog posts. Impressive, coz “wow I was so smart a few months ago and had figured this out!” but scary coz “shit, I’ve forgotten I ever did this”. 😵‍💫

Case in hand, yesterday I blogged out the HTTP with Azure AD connector. I had forgotten there was a blog post on it, and while I was looking to do some more stuff with it today I came across two more on my side.

  • This one, which is super informative on what else one can use that connector for. Heck, I even figured out how to use it for Forms and other stuff – sure, it was about 9 months ago and I have been busy with other stuff so don’t really remember it, but still…
  • And this one, which is quite cool in that I use this connector to make requests to my Logic Apps.

Between these two posts, I think they capture everything I know (or should know) about this connector.

Anyways, a colleague wanted to use the HTTP with Azure AD connector to get reports. The account in question has the Reports Reader role, and he was trying the following URL: https://graph.microsoft.com/v1.0/reports/getOffice365GroupsActivityDetail(period='D90')

But it failed:

It’s not surprising it failed. The connector has a limited set of scopes:

That is to say, it probably doesn’t have the Reports.Read.All delegated permission assigned to it, so even though the account can do it the connector can’t. Bummer.

I could go the route of an HTTP connector, and I did spend some time fooling around with that, but eventually gave up. Using an HTTP connector has the draw back that I need to send the username and password as part of the password flow. There are options to authenticate via OAuth 2.0 with the HTTP connector but I couldn’t quite figure out how to make it work with an App Registration I created that gives the delegated Reports.Read.All permissions. Could be that I was just being thick when figuring it out.

Anyways, using a custom connector is more fun. And reusable. Plus I can setup the connector for others in their environment, without handing over the secret to others. Way more nifty in my opinion.

So here’s what I did.

First, I created an App Registration. Just a standard one, but I added the delegated Reports.Read.All permission to it and did an admin consent.

Then I created a secret, and noted that.

Next, I went to Power Automate and created a custom connector.

Go to Data > Custom Connectors > New custom connector.

Create from blank. Give it a name.

And description. And change host to graph.microsoft.com.

Then go to the next page, Security.

I want OAuth 2.0 and Azure AD.

Fill in the rest of the details from the App Registration. I left the Tenant ID as common, but filled in the resource URL as https://graph.microsoft.com (fill that exactly; I know this experience).

Click “Create Connector”.

That should generate a Redirect URI.

I added that to the App Registration.

Go to the next section, Definition.

Add an action.

Update: Later, I changed the above to be like this:

That’s because I realized there’s no way to take an input of the number of days, so I might as well create separate ones.

And a request, for that action.

The URL is https://graph.microsoft.com/v1.0/reports/getOffice365GroupsActivityDetail

Update: Later, I changed the URL to be https://graph.microsoft.com/v1.0/reports/getOffice365GroupsActivityDetail(period='D90') to match the updated name above

Click on Response, add a default response,  and I added the following:

I got this from the API documentation.

This one’s a bit odd in that the response is the headers.

And that’s it, save/ update the connector.

Now in my Power Automate, I can call this custom connector.

After selecting I can see the action I created.

Save, and test/ run it.

On the face of it, it throws an error.

But that’s misleading, because the headers show the content I want:

So I create a Compose action after this connector, and use the Location as input.

And set it to run even after the connector has failed.

Run it now, and the flow succeeds. 🙂

I downloaded the JSON and added more actions to it.

These are all the valid “period” options for that call. I wish there was some way to take an input for GET operations. There is, if I switch to POST, but that’s not what I need.

Update: Later in the day, when Googling on something else, I came across this blog post from Microsoft. It’s a good one. And worth noting this point:

Custom connectors are supported by Microsoft Azure API Management infrastructure. When a connection to the underlying API is created, the API Management gateway stores the API credentials or tokens, depending on the type of authentication used, on a per-connection basis in a token store. This solution enables authentication at the connection level.