DEV Community

loading...
Cover image for Secure APIs with Azure Functions, Java, Azure AD… and MS Graph!
The 425 Show

Secure APIs with Azure Functions, Java, Azure AD… and MS Graph!

Christos Matskas
Program Manager in the Microsoft Identity Dev Advocacy team. Programmer, speaker and all around geek
Updated on ・8 min read

As a .NET developer, it’s always fun to step out of my comfort zone and play with other languages and frameworks. Beyond the extra knowledge and experience, it also gives you a new perspective on how good or bad your favorite tools, language and frameworks. This time around, for our Tuesday Twitch stream, we decided to delve into uncharted territory and work with Java in Azure Functions. And to make this scenario more complete, I wanted to secure my Java API with MSAL4J call into Azure AD and MS Graph.

You can watch the whole stream and the step by st

Getting started with Java and Azure Functions

The developer experience for Azure Functions is great! I love that you can develop and test Functions locally with your favorite IDE (Visual Studio, Visual Studio Code or JetBrains Rider) or even a standard text editor and the Azure Functions Core tools. For this stream/blog, we decided to use VS Code with Java. There are, however, some additional prerequisites in order to be able to work with Java Functions. I found it best to use the team’s recommendations in the Getting Started doc. There was, however, one thing left out and that’s the Azure Function Core tools that you need to install separately via NPM or Chocolatey (if you’re on Windows).

With all the necessary software/tools installed, we can now go ahead and create our Azure Function using VS Code. First, we should create a folder that will be used as our workspace. This is where all our Function code will go. Let’s name it java-func-auth. In VS Code, open the Azure Functions* panel, and click on the **Create New Project button.

Alt Text

Select the folder we just created and choose Java for the Function language:

Alt Text

Select all the defaults and choose Open in Current Window

Alt Text

Assuming that all the prereqs are met, we can see how our Function is created in the Output window

Alt Text

In the Explorer panel, we expand the src folder to see the actual Function code. There is the main Function code and a corresponding test. That’s a nice touch from the Azure Functions team as it helps us get started with writing robust, testable code.

Alt Text

Before we start adding any code, let’s run the Function to ensure that everything is working as expected. No point in moving forward if the vanilla code doesn’t run due to missing dependencies etc. VS Code will clean the project, rebuild and launch our Java Function:

Alt Text

You can use curl, Postman or the browser to hit the Function endpoint. The default URL is http://localhost:7071/api/HttpExample so to get a response from our Function we need to pass a name parameter. The right URL to use is http://localhost:7071/api/HttpExample?name=425Show

Alt Text

Grab the auth token from the HTTP header

Since this needs to be a secure API, we expect a token to be passed to the Function via the HTTP request headers. With this in mind, we need to write some code to grab the token from the header so that we can validate it later.
Let’s create a new method called GetJwtFromHeader() using the code below:

private static String getJwtFromHeader(final HttpRequestMessage<Optional<String>> request) {
        final Map<String, String> headers = request.getHeaders();
        final String authHeader = headers.get("authorization");
        final String[] authHeaderParts = authHeader.split(" ");
        return authHeaderParts[1];
}

We should now update the main Function code to retrieve the JWT from the header and return an error message if the auth header is missing

@FunctionName("GetGraphDataForUser")
    public HttpResponseMessage run(@HttpTrigger(name = "req", methods = { HttpMethod.GET,
            HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) final HttpRequestMessage<Optional<String>> request,
            final ExecutionContext context) throws Exception {
        context.getLogger().info("Java HTTP trigger processed a request.");

        final String jwt = getJwtFromHeader(request);
        if (jwt == null) {
            return request.createResponseBuilder(HttpStatus.UNAUTHORIZED)
                .body("No Authorization header present")
                .build();
        }
        return request.createResponseBuilder(HttpStatus.OK).body(“Well done”).build();
    }

Changing the Function code means that our tests will also need to be updated because attempting to launch the Function locally now will fail due to failed tests. Open the FunctionTest.java file and add the following code:

final Map<String, String> headers = new HashMap<>();
        headers.put("authorization", "Bearer RandomValue");
        doReturn(headers).when(req).getHeaders();

Let’s launch our Function and test it. Since we now need to pass an Authorization Header, I will switch to Postman so that I can easily test the code:

Alt Text

Add the MSAL4J library

For the next step we will be adding the token validation and the call to MS Graph. To make this more straightforward, Microsoft has release the MSAL4J library. Since our Java Function is designed to work with Maven, we can add this as as a dependcy to our project by updating pom.xml file with the following:

<dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>msal4j</artifactId>
    <version>1.6.1</version>           
</dependency>

Upon saving the file, VS Code will prompt us to update the project and download the dependencies – I love it!

Create the Azure AD app registrations

To be able to validate our token and call the MS Graph API, we need to create a couple of App Registrations in Azure AD. The first app registration will be used by our Azure Function to validate the token and call MS Graph. The second one will be used by our client app. The client can be any app that needs to interact with our Azure Function in an authenticated manner.

Create the Java Func API registration

In the Azure portal, we need to navigate to Azure AD -> App Registrations and Create new App Registration: java-func-api. To provide access to MS Graph we need to add the right permissions to our app. Let’s open the API permissions tab to ensure that Microsoft.Graph->User.Read permission is present. If not, we’ll need to add it. It’s also important to provide Grant admin Consent because the API doesn’t provide a user-interactive way to consent to these permissions. Therefore, we need to do this in advance:

Alt Text

We also need to make sure that this API is accessible by other client apps. We do this via the Expose an API tab. We configure the scopes that the client apps will be requesting via the Add Scope button. Configure the app as per the image below:

Alt Text

Finally, we need to create a new Secret that we will be using the Certificates & Secrets tab

Alt Text

Make a note of the secret as you won’t be able to retrieve it again once you navigate away. Yet again, you can always create a new one.

Create the Java Func Client registration

We also need to create an App Registration for our client app. We will be using this with Postman to first authenticate against Azure AD and then send authenticated requests to our Java Function.

In the Azure Portal -> Azure AD -> App registration, we register a new app: java-func-client. We then need to create a secret in the Certificates & Secrets tab.

Finally, we need to add the API permissions to our java-func-api. In the API permissions press the Add a permission button and then select My APIs.

Alt Text

From there, select the java-func-api and add the permission to the Functions API app registration which should be access_as_user. Make sure to press the Add permissions button at the bottom of the popup tab.

Alt Text

We are now done with the Azure AD portal.

Using MSAL4J to validate tokens and call MS Graph

To achieve our token validation and call to MS Graph, we are going to add 2 new methods to our Java Function: ValidateToken() and CallMicrosoftGraphEndpoint(). The code for both is below:

private static IAuthenticationResult ValidateToken(final String authToken) throws Exception {
    final IClientCredential credential = ClientCredentialFactory.createFromSecret(CLIENT_SECRET);
    final ConfidentialClientApplication cca = ConfidentialClientApplication.builder(CLIENT_ID, credential)
                .authority(AUTHORITY).build();
    IAuthenticationResult result;
    try {
        final OnBehalfOfParameters parameters = OnBehalfOfParameters
           .builder(SCOPE, new UserAssertion(authToken)).build();
        result = cca.acquireToken(parameters).join();
    } 
    catch (final Exception ex) {
            throw ex;
    }
    return result;
}

The above method creates a ConfidentialClientApplication which then goes to AAD and acquires a token for us to be able to access the graph. If the authToken passed is invalid/expired or contains the wrong scopes, the call result will be a 401 or a 403 error, respectively.

private java.net.http.HttpResponse<String> callMicrosoftGraphMeEndpoint(final IAuthenticationResult authResult) throws Exception{
    java.net.http.HttpResponse<String> response = null;
    try {
        java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder()            
           .uri(URI.create("https://graph.microsoft.com/beta/me"))
           .timeout(Duration.ofMinutes(2))
           .header("Content-Type", "application/json")
           .header("Accept", "application/json")
           .header("Authorization", "Bearer " + authResult.accessToken())
           .build();

           final HttpClient client = HttpClient.newHttpClient();
           response = client.send(request, BodyHandlers.ofString());
    }
    catch (Exception e) {
        e.printStackTrace();
    }
    return response;
}

The above method uses the access token we just acquired to make an authenticated HTTP call to MS Graph to retrieve the user’s profile data.

Use Space + . to resolve all the dependencies. At the top of the our class, we need to add a few constants (private final static in Java) to store our Azure AD App Registration information:

private final static String CLIENT_ID = "84267134-0000-0000-ad1a-6ca96fde3f96";
private final static String AUTHORITY = "https://login.microsoftonline.com/<yourTenantName>.onmicrosoft.com";
private final static String CLIENT_SECRET = "<your java-func-api Secret>";
private final static Set<String> SCOPE = Collections.singleton("https://graph.microsoft.com/user.read");

Finally, we need to update our main Function method to use these two methods to retrieve the MS Graph data for the currently authenticated user using the OnBehalf flow:

Replace this line:

return request.createResponseBuilder(HttpStatus.OK).body(“Well done”).build();

With these 3 lines

final IAuthenticationResult authResult = ValidateToken(jwt);
java.net.http.HttpResponse<String> response = callMicrosoftGraphMeEndpoint(authResult);
return request.createResponseBuilder(HttpStatus.OK).body(response.body()).build();

Note: you may have noticed that we need to fully qualify some variables. This is because these objects exist both in the Java util and MSAL4J libraries and we need to resolve the ambiguity like this.

In Postman, we first need to get a new access token in the Authorization tab. Use this token to update the token in our test code because otherwise the tests will fail with invalid token. For now we only want to test that our code works but eventually we’ll need to fix our tests to use a mock token.

Save and launch the Java Function locally using VS Code. Back in Postman, when we make a GET request to the Function endpoint we should receive our Graph data as shown in the picture
Image

Summary

Working with Azure AD and MS Graph with Java in Azure Functions via MSAL4J is straightforward even for someone that's not a hardcore Java developer. With a few lines of code we were able to grab the auth token from the incoming request header, pass it to MSAL4J to acquire an access token and then use the new token to call MS Graph all inside a Function.

You can fork and run the full sample project from GitHub

Stay connected!

We stream live twice a week at twitch.tv/425Show! Join us:

7am-10am Pacific US time Tuesdays
8am-9am Pacific US time Thursdays for Community Hour

You can also hanging out with us on Discord if you have any questions or want to stream with us on Twitch

Until next time,
CM

Discussion (0)