Here we register an application with Azure AD and wrote a simple web application to authenticate users via their Azure credentials.
Every developer has to address authentication and authorization at some point in enterprise application development. These security concerns are complex problems to solve. Yet, generally speaking, most applications have similar requirements. These include:
- Logging users into and out of an application
- Resetting passwords
- Managing user permissions
- Supporting logins from existing accounts, including Google, Facebook, Microsoft, and others
- Supporting additional security layers, such as two-factor authentication
Azure Active Directory (Azure AD) provides scalable and secure identity platforms and has rich integrations with third-party APIs and identity providers. The Microsoft Authentication Library (MSAL) offers native integration with Azure AD for various languages and frameworks. The combination of Azure AD and MSAL offers developers a turn-key solution for implementing authentication and authorization in our applications.
In this first tutorial of a three-part series, we’ll explore integrating Azure AD and MSAL with a Spring Boot web application. This process involves:
- Registering a new Azure AD application
- Creating a new application secret
- Bootstrapping a Spring application
- Configuring the Spring application with the Azure AD tenant
- Logging in with an external account and inspecting the logged-in user’s details
Find this tutorial’s frontend application source code in the mcasperson/SpringMSALDemo GitHub repo under the initial-integration branch. To follow along, you should know Java.
Registering an Azure AD Application
Our application needs to be registered with Azure AD to manage user logins and perform other user management tasks. If you don’t already have an Azure account, sign up to access services like Azure AD. You get 50,000 stored objects monthly with a single sign-on (SSO) to all cloud apps for free.
Start by signing into the Azure Portal. Then, use the search bar at the top of the screen to search for Azure Active Directory:
To register our application with Azure AD, we click the App Registration link in our Azure Active Directory left-hand menu, then click the New registration button:
Then, we name the application:
Next, we enter http://localhost:8080/login/oauth2/code/ for the Redirect URI. A Spring Boot application integrated with MSAL exposes this default URI.
Then, we click the Register button:
Make a note of your Directory (tenant) ID and Application (client) ID, as you’ll need these values later on.
To connect our code to the application, we need to generate a secret. To do this, we click the Client credentials link:
Then, we name the secret and click the Add button:
Make a note of your secret Value, as Azure won’t show it again once you’ve left this screen:
Building the Spring Boot Application
With Azure AD now configured, we’re ready to start building our Spring application. We use Spring's online tool as a starting point for our code.
Bootstrapping the Spring Application
We start by open Spring Initializr, an online tool for creating starter Spring projects to bootstrap our application. We select options there to build a JAR file with Maven and Java 17, using the latest non-snapshot version of Spring. We also click ADD DEPENDENCIES to add the following three dependencies:
- Spring Web, a built-in webserver to host our application
- Thymeleaf, a template language for displaying dynamic web pages
- Azure Active Directory, enabling us to integrate with Azure AD
Next, we click the GENERATE button to download a ZIP file with the bootstrapped project.
Configuring the Azure AD Settings
We need to configure the Azure application in the application.yml file. The contents of this file are as follows:
# src/main/resources/application.yml
azure:
activedirectory:
tenant-id: ${TENANT_ID}
client-id: ${CLIENT_ID}
client-secret: ${CLIENT_SECRET}
logging:
level:
org:
springframework:
security: DEBUG
Let’s break down these values:
tenant-id
is the Tenant ID we used to register our Azure AD application. We’ll define this value as an environment variable called TENANT_ID. client-id
is the Client ID from our Azure AD application. We’ll define this value as an environment variable called CLIENT_ID. client-secret
is the secret we created for the Azure AD application. We’ll define this value as an environment variable called CLIENT_SECRET. - The Spring Security library is set to print logs at the DEBUG level, which helps diagnose issues with logins and permissions.
Building the Controllers
We need to add controllers to our application to respond to requests from a browser. Spring controllers are regular classes enriched with Spring annotations to integrate them into the Spring Web framework.
The first controller displays the root directory.
Here we have a class called HomeController. It’s annotated with @Controller
, with a single method called main
annotated with @GetMapping
. The parameter passed to the @GetMapping
annotation defines the path that this method responds to for HTTP GET requests. Here, we use a single forward slash to indicate that this method responds to requests for the root directory.
The main
method returns a string indicating the filename to return to the browser. The filename has no extension, and Spring finds the appropriate file for us. Here’s the controller’s complete code:
package com.matthewcasperson.demo.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String main() {
return "index";
}
}
Our application has a second page displaying the details of the currently logged-in user Azure AD. To display this page, we create a second controller called ProfileController. The complete code is:
package com.matthewcasperson.demo.controllers;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class ProfileController {
@GetMapping("/profile")
public ModelAndView profile(
@AuthenticationPrincipal OidcUser principal) {
ModelAndView mav = new ModelAndView("profile");
mav.addObject("tokenAttributes", getObjectAsJSON(principal.getAttributes()));
return mav;
}
private String getObjectAsJSON(Object attributes) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
try {
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(attributes);
} catch (JsonProcessingException e) {
return "{\"message\":\"" + e + "\"}";
}
}
}
Let’s look at this code.
As with the previous controller, ProfileController
is a class annotated with @Controller
:
@Controller
public class ProfileController {
A method called profile
responds to requests to the /profile
URL and populates a ModelAndView
object.
A ModelAndView
contains attributes the view consumes, allowing us to inject dynamic values into a template. Here, we create a ModelAndView
object displaying the template called profile
and a model attribute called tokenAttributes
containing a JSON representation of the attributes assigned to the currently logged-in user:
@GetMapping("/profile")
public ModelAndView profile(
@AuthenticationPrincipal OidcUser principal) {
ModelAndView mav = new ModelAndView("profile");
mav.addObject("tokenAttributes", getObjectAsJSON(principal.getAttributes()));
return mav;
}
The getObjectAsJSON
method converts any object into its JSON representation using the Jackson library:
private String getObjectAsJSON(Object attributes) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
try {
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(attributes);
} catch (JsonProcessingException e) {
return "{\"message\":\"" + e + "\"}";
}
}
}
Building the Frontend HTML
We won’t dive into the frontend HTML this application uses, as it’s comprised of standard HTML, CSS, and JavaScript. The only exception to this is the Thymeleaf template syntax that displays the model-stored attributes. We’ll dive into this below.
You can find the CSS and JavaScript files in the src/main/resources/static directory and the HTML templates in the src/main/resources/templates directory.
Let’s look at how the tokenAttributes
model attribute, defined in the profile
method of the ProfileController
class, is consumed.
The profile.html page includes the <pre> block shown below. Thymeleaf modifies HTML elements using custom attributes, and here we use the th:text
attribute to populate the <pre>
element text with the tokenAttributes
model attribute’s value:
<pre th:text="${tokenAttributes}"></pre>
Customizing the Security Rules
The default Spring security rules require users to log in to access any page or its supporting CSS and JavaScript files. This setup is a little too restrictive for us, so let’s define custom rules that allow unauthenticated users to view the root directory and require users to log in to view the profile page. We define these rules in the AuthSecurityConfig class:
package com.matthewcasperson.demo.configuration;
import com.azure.spring.aad.webapp.AADWebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@EnableWebSecurity
public class AuthSecurityConfig extends AADWebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/", "/login", "/*.js", "/*.css").permitAll()
.anyRequest().authenticated();
}
}
The class is annotated with @EnableWebSecurity
to enable Spring Security and extends AADWebSecurityConfigurerAdapter
, which provides us with the configure
method to define the security rules:
@EnableWebSecurity
public class AuthSecurityConfig extends AADWebSecurityConfigurerAdapter {
We define our application’s security rules in the configure
method.
We start by calling the super
class. This class allows the AADWebSecurityConfigurerAdapter
class to apply the settings the MSAL library requires.
We then call the HttpSecurity
object passed into the method, which exposes a fluent interface for configuring application security.
The authorizeRequests
method exposes child methods, enabling us to restrict or permit access to paths our application reveals. The application applies the first matching rule defined under authorizeRequests
.
Calling antMatchers
allows us to list paths that will have rules applied, and permitAll
enables unauthenticated users to access the listed paths. We allow all access to the root directory, the /login path, and CSS and JavaScript files.
The anyRequest
method matches all requests made to the application. We use this to catch all other requests not granted above. Calling authenticated
ensures the user must be logged in to make these requests:
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/", "/login", "/*.js", "/*.css").permitAll()
.anyRequest().authenticated();
}
Running the Spring Application
To build and run the application, we run the following PowerShell:
$env:CLIENT_SECRET="Application client secret"
$env:CLIENT_ID="Application client ID"
$env:TENANT_ID="Azure AD tenant ID"
.\mvnw spring-boot:run
Or Bash:
export CLIENT_SECRET="Application client secret"
export CLIENT_ID="Application client ID"
export TENANT_ID="Azure AD tenant ID"
./mvnw spring-boot:run
We next open http://localhost:8080/profile. Our browser redirects us to log in using an existing Microsoft account:
Once we approve access, our browser redirects to our Spring application.
The profile page then shows the attributes assigned to the currently logged in user:
Below is an example attributes list:
{
"sub" : "EwG-OFUmFOJpv4JUUA0lDtl9QDFKkU9BMkIrIh17aTM",
"ver" : "2.0",
"iss" : "https://login.microsoftonline.com/2ed832a8-cd8c-4f7d-89b3-935447e260c8/v2.0",
"oid" : "4c037c2f-4b2f-46c1-a25f-d214e2396d47",
"preferred_username" : "matthewcasperson@matthewcasperson.onmicrosoft.com",
"uti" : "JmhbvuE6ZUGorI55pGkqAA",
"nonce" : "JQTmD6Yr2SDmxmymWXeSX98pWzQy8UOIwWhQDNwpnrc",
"tid" : "2ed832a8-cd8c-4f7d-89b3-935447e260c8",
"aud" : [ "b31490c0-4ac4-4f8c-8f8e-d5addb72271d" ],
"nbf" : 1632542348000,
"rh" : "0.AUEAqDLYLozNfU-Js5NUR-JgyMCQFLPESoxPj47VrdtyJx1BACU.",
"name" : "Matthew Casperson",
"exp" : 1632546248.000000000,
"iat" : 1632542348.000000000
}
Next Steps
In this tutorial, we registered an application with an Azure AD tenant, created a simple Spring Boot application that logged in via Azure AD, and displayed information about the currently logged-in user.
In the next article of this three-part series, we’ll use the access token this application receives to make requests to another Spring-based microservice. That microservice acts on the user's behalf to get calendar events from their Office 365 account. Continue this tutorial in the following article, Using MSAL with the Microsoft Graph and Office 365 Data.
Further Reading