Use Azure Function and API Management Authorizations to create your own service connector
Published Apr 26 2023 08:00 AM 6,947 Views
Microsoft

Web APIs have experienced an exponential increase in popularity and usage in the past few years. APIs exist at the intersection of business, products, and technologies and have transformed the way businesses interact with each other and the way they provide value to their customers. Web APIs allow businesses to access 3rd-party data, allow for cross-platform communication and seamless integration anywhere and anytime it's required, offering unmatched data processing efficiencies and cost savings.

 

Azure API Management

Azure API Management accelerates the deployment, monitoring, security, and sharing of APIs in a dedicated network. It is a way to create consistent and modern API gateways for back-end services. Authorizations (along with authentication) is an important security component of your development process because it enables organizations to keep their networks secure by permitting only authenticated users (or processes) to access protected resources. Implementing authentication requires to understand these concepts and is not only very time consuming but also comes with its challenges.
 

API Management Authorizations

Authorizations in API Management is a simple and reliable way to unbundle and abstract authorizations from web APIs. It greatly simplifies the process of authenticating and authorizing users across one (or more) backend or SaaS services. With Authorizations you can easily configure OAuth, Consent, acquire tokens, cache tokens and refresh tokens without writing a single line of code. It allows you to delegate authentication to your API Management instance. This feature enables APIs to be exposed with or without a subscription key, and the authorization to the backend service uses OAuth 2.0., and reduces development costs in ramping up, implementing and maintaining security features with service integrations. API Management does all the heavy lifting for you, while you can focus on the application/domain logic.
 

Authorization scenarios

When we look at scenarios with Authorizations, we can distinguish between two types: attended scenarios (human-initiated) and unattended (fully automated) scenarios. During our last blog post Use Static Web Apps API and API Management Authorizations to integrate third party services, we focused on an attended scenario with Azure Static Web Apps:
Attended Scenario.png

Today, we will talk about an unattended scenario with Azure Functions. With our Static Web App Scenario, users are able to post a GitHub issue to a repository. We now want to implement a timer triggered Azure Function that will GET the count of GitHub issues and POST about it in a Microsoft Teams channel. This will create a reminder notification in Teams about how many issues are still open:

Unattended Scenario (1).png

Prerequisites

STEP 1 - Configure Authorizations in Azure API Management

For our scenario, we need two API Management Authorizations, one for the GitHub API and one for the Microsoft Graph API.

For the GitHub authorization, you can follow this tutorial to configure your authorization. Make sure you use the following configurations:

Settings
Value
Provider name
githubissue01
Identity provider
Select GitHub
Grant type
Select Authorization code
Client id
Create a new GitHub OAuth app or use existing one from Blog Post
Client secret
Paste the value from the GitHub OAuth app
Scope
repo
Authorization name
githubissue01

 

For the Microsoft Graph API authorization, you can follow this tutorial to configure your authorization. Make sure you use the following configurations:

 

Settings
Value
Provider name
channel-aad
Identity provider
Select Azure Active Directory
Grant type
Select Authorization code
Client id
Paste the value you copied earlier from the app registration - follow tutorial for setting this up
Client secret
Paste the value you copied earlier from the app registration
Resource URL
Scopes
Note: Leave this input empty since your scopes are defined in the app registration
Authorization name
channel-aad

 

STEP 2 - Add your GitHub API and configure a policy

For the GitHub API, we want to add the following API:
Setting
Value
Display name
githubissue
Name
githubissue
Web service URL
API URL suffix
githubissue
 
Setting
Value
Display name
getissues
URL for GET
/repos/{github-alias}/{reponame}/issues
JuliaKa_0-1679443892659.png
Once you added the API, we can make use of the provider in the Inbound Processing Policy and apply the previously created Authorization. Add the following snippet to the inbound JWT policy:
<policies>
    <inbound>
        <base />
        <get-authorization-context provider-id="githubissue01" authorization-id="githubissue01" context-variable-name="auth-context" identity-type="managed" ignore-error="false" />
        <set-header name="Authorization" exists-action="override">
            <value>@("Bearer " + ((Authorization)context.Variables.GetValueOrDefault("auth-context"))?.AccessToken)</value>
        </set-header>
        <set-header name="User-Agent" exists-action="override">
            <value>API Management</value>
        </set-header>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

 

STEP 3 - Add your Microsoft Graph API and configure a policy

For the Microsoft Graph API, we want to add the following API:
Setting
Value
Display name
TeamsChannelMessage
Name
TeamsChannelMessage
Web service URL
API URL suffix
TeamsChannelMessage
 
Setting
Value
Display name
postchannelmessage
URL for POST
/v1.0/teams/{team-id}/channels/{channel-id}/messages
JuliaKa_1-1679444028289.png

Once you added the API, we can make use of the provider in the Inbound Processing Policy and apply the previously created Authorization. Add the following snippet to the inbound JWT policy:

<policies>
   <inbound>
       <base />
       <get-authorization-context provider-id="channel-aad" authorization-id="channel-aad" context-variable-name="auth-context" identity-type="managed" ignore-error="false" />
       <set-header name="authorization" exists-action="override">
           <value>@("Bearer " + ((Authorization)context.Variables.GetValueOrDefault("auth-context"))?.AccessToken)</value>
       </set-header>
   </inbound>
   <backend>
       <base />
   </backend>
   <outbound>
       <base />
   </outbound>
   <on-error>
       <base />
   </on-error>
</policies>
NOTE: For more information, check out the get-authorization-context policy references to learn more about how to use the policy.

STEP 4 - Test the APIs

  1. Select your API and the operation you added previously
  2. Go to the Test tab.
  3. Select Send.

JuliaKa_1-1679444378373.png

 

STEP 5 - Building your timer triggered Azure Function

Next, we will build our timer triggered function in Azure Functions. For this, you can follow the Quickstart: Create your first C# function in Azure using Visual Studio and use the AuthorizationsDemoAzureFunction GitHub repoWe used the following configurations in Visual Studio:

Setting
Value
Project name
FunctionAppAPIMAuthTest
Solution name
FunctionAppAPIMAuthTest

JuliaKa_2-1679444460666.png

Make sure to use Time trigger as the initial trigger for your Azure function.

JuliaKa_3-1679444478078.png

In local.settings.json file, we will add the following code:

{
    "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "CUSTOM_URL": "YOUR_CUSTOM_URL",
    "SUBSCRIPTION_KEY": "YOUR_SUBCRIPTION_KEY",
    "TEAMS_URL": "YOUR_TEAMS_URL"
  }
}

In our FunctionAppAPIMAuthTest.cs file, we will add the following code:

using System;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;

namespace FunctionAppAPIMAuthTest
{
    public class Function1
    {
        private static HttpClient httpClient = new HttpClient();

        [FunctionName("Function1")]
        public async Task Run([TimerTrigger("*/1 * * * *")] TimerInfo myTimer, ILogger log)
        {
            log.LogInformation($"Timer trigger function started at: {DateTime.Now}");

            //Define header
            var subscriptionKey = System.Environment.GetEnvironmentVariable("SUBSCRIPTION_KEY", EnvironmentVariableTarget.Process);
            httpClient.DefaultRequestHeaders.Accept.Clear();
            httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);

            //Define GET URL for GET issue count
            var baseurl = System.Environment.GetEnvironmentVariable("CUSTOM_URL", EnvironmentVariableTarget.Process);
            var addurl = "?state=open";
            var _baseurl = baseurl + addurl;
            log.LogInformation($"URL: {_baseurl}");

            //GET call
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, _baseurl);
            var response = await httpClient.SendAsync(request);
            JArray arr = JArray.Parse(await response.Content.ReadAsStringAsync());

            //Error handling
            log.LogInformation($"You currently have {arr.Count} issues open.");
            if (!response.IsSuccessStatusCode)
            {
                throw new Exception($"Call unsuccessful: {response.IsSuccessStatusCode}");
            };

            //Define POST URL to POST TeamsBody to Teams
            var teamsurl = System.Environment.GetEnvironmentVariable("TEAMS_URL", EnvironmentVariableTarget.Process);
            log.LogInformation($"URL: {teamsurl}");

            // Create TeamsBody
            var TeamsBody = new
            { 
                body = new
                {
                    content = $"You currently have {arr.Count} issues open."
                }
            };
            log.LogInformation($"Body: {TeamsBody.body.content}");

            //Define POST header
            httpClient.DefaultRequestHeaders.Accept.Clear();
            httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);

            //POST call with TeamsBody content
            HttpRequestMessage requestteams = new HttpRequestMessage(HttpMethod.Post, teamsurl);
            requestteams.Content = new ObjectContent<object>(TeamsBody, new JsonMediaTypeFormatter());
            var responseteams = await httpClient.SendAsync(requestteams);
            log.LogInformation($"Response: {responseteams.IsSuccessStatusCode}");

            //Error handeling
            if (!responseteams.IsSuccessStatusCode)
            {
                throw new Exception($"Call unsuccessful: {responseteams.IsSuccessStatusCode}");
            };
        }
    }
}
Note: Function1 referring to your function class file.

STEP 5 - Test Azure Function locally

Now, we can test our Azure Function locally. Hit F5 or PLAY button and Run the function locally. You should see a message within your Microsoft Teams channel similar like this:
JuliaKa_5-1679444801371.png 
Let us know what you think! @Julia Kasper and @Thiago Almeida
 

Further Resources

 

1 Comment
Co-Authors
Version history
Last update:
‎Apr 24 2023 11:53 AM
Updated by: