Secure Spring Boot Application with Microsoft Azure AD

Łukasz Hyła from Iterative Engineering
Łukasz Hyła Senior Software Engineer @ Iterative Engineering

Application security is an important part of cohesive digital product security and one for which typically software developers are responsible for. Handling it properly and according to industry standards is crucial, especially nowadays where literally anything that gets published is immediately targeted and scanned for vulnerabilities.

One of the most common challenges in that aspect is authentication & authorization of the users. While there are many ways to do it, its really worth considering an external Identity Provider approach either via OAuth2 or SAML. The reason is that it typically gives great and proven security without the need to code and maintain the whole process, which is not trival!

This article presents an in detail walkthrough on how to secure a Java Spring Boot Application using Microsoft Azure AD, which is a great and widely adopted example of a reliable Identity Provider.

Who may find this article helpful

  • Java developers experienced with Spring looking for ‘how to’ on pluging in Azure AD as Identity Provider into their apps.
  • Software Developers wishing to increase their knowledge around applications security and authentication schemes.

If you belong to first group you may skip the first part and jump directly to the “Register application in Azure Tenant” chapter.

What will you learn

  • What are the advantages and disadvantages of using an external Identity Provider
  • How to configure and register your application in the Azure Tenant which is required to secure it by Azure AD
  • How to configure Spring Boot Application to secure it via Microsoft Identity Provider
  • How to configure Nginx / Apache as a reverse proxy when secured as such

Example project

One to be found on GitHub: Spring Boot Secured App

Prerequisites

  • Azure account with a subscription (e.g Pay as you go)
  • Java 11 or higher (or a docker container with such)

Self-implemented Authentication & Authorization

Imagine that in 2010 you have to prepare an application for e.g company managers. With different access levels. What would be your approach to achieve that? Most probably you would go with something similar to:

  1. Create a table of users + passwords and roles in the application
  2. Prepare admin panel that allows to manage users and their privileges
  3. Manage sessions (either as cookies or tokens) on the client web-app side and store them on the application server, e.g in memory or in DB
  4. Implement features such as: Password restore

A self baked authentication & authorization system with local user information storage. Such setup, even today, is a common way to craft a prototype or MVP and it has advantages:

  • low complexity - no 3rd party libraries, no external services engaged
  • data control - all users data are available and may be used the application

However, it has also major disadvantages:

  • vulnerability - Important user data are stored within the application. So they have to be well secured
  • maintenance problems - Roles/groups have to be managed in the app, not in a central point
  • limited extendability - Each feature such as 2FA, Single Sign-On have to be implemented, while implementing security mechanisms is costly and time consuming
  • lack of 3rd party systems integrations - For instance logging via Facebook or Google have to be implemented within the application. While services like Azure AD provide easy-to-use integrations.

In the long term maintenance efforts could be significant, while not giving any benefits. So let’s look at alternatives.

Identity providers come on stage

In 2007 OAuth 1.0 protocol was released and in 2012 its new version: Oauth 2.0 was released. This started a popularization process of external identity providers - systems that keep users data and provide these data to business applications with user acceptance.

OAuth 2.0 is widely supported by Microsoft, Google, Amazon etc. The solution I present in this article, while aimed at Microsoft Azure, could be implemented with other identity providers with minor tweaks.

How to secure Spring Boot with Azure AD

Let’s go step by step through the configurations that have to be applied in the Microsoft Azure Portal, to secure the application using the Azure Active Directory.

  • Register application in Microsoft Azure Tenant
  • Create & Configure Java (Spring Boot) Application
  • Configure reverse proxy
  • Authenticate to the app (Test the setup)

Register application in Azure Tenant

Here are steps that are presented below on screenshots:

  • Create a new Tenant
  • Register a new application & set its allowed ‘redirect-uri’
  • Generate application secret

Setup a new Tenant

  1. Log in to portal.azure.com and search for Azure Active Directory service:

    Search for Azure Active Directory

  2. Select manage tenants:

    Select manage tenants

  3. Select basic tenant:

    Select basic tenant

  4. Finish the configuration and create it:

    Finish the configuration and create it

  5. Once ready copy the tenant ID, as it will be neccessary further:

    Once ready copy the tenant ID

Register a new application & set its allowed ‘redirect-uri’

  1. Select “App registrations” and “Register an application”

    Register a new application via App registration

  2. Define app name and set redirect URI:

    Define app name and redirect URI

  • Name: Users will see this name e.g on the “Grant permissions” screen.
  • Supported accounts types: By choosing option 1 only you and users from your tenant will be able to log in to the application. As you see, you can allow logging in to your application even for users who are not part of your organisation (option 2,3,4). Choose these options wisely, and only if your application does not expose any vulnerable data.
  • Redirect URI: In our example it’s not optional. It’s the URL to which the user is redirected after a successful authentication on the Microsoft login page. If you deploy your application not on localhost but on some server, change localhost:8080 to the appropriate domain name.

Create a Secret

  1. Generate a new client secret Add certificate or secret

    Create new client secret

    Note: All secrets have to have an expiration date and they can’t be renewed, so if a secret expires, you can generate a new secret and update it in your application.

  2. Copy and save the Secret value (it is visible only just after secret creation)

    Copy and save generated Secret

Never reveal the Secret to anyone nor publish it to public repositories!

Great! At this point the setup on the Azure side is now complete, let’s move on to the application.

Configure Spring-Boot application

Key things that have to be created:

  • Dependency to the azure spring-boot library that does the ‘magic’ and simplifies integration between SpringBoot app and our Azure Tenant
  • Properties that allow the app to connect to the Azure Tenant
  • Rest controller that will be used for tests

Here is an example build.gradle file, but a similar setup could be done in maven pom.xml.

build.gradle

plugins {
  id "org.springframework.boot" version "2.5.0"
  id 'io.spring.dependency-management' version '1.0.6.RELEASE'
  id 'java'
}

group = 'pl.iterative'

version = '1.0.0-SNAPSHOT'

repositories {
  mavenLocal()
  mavenCentral()
  gradlePluginPortal()
}

dependencies {

  // Spring boot
  implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
  implementation 'org.springframework.boot:spring-boot-starter-web'

  // Azure
  implementation 'com.azure.spring:azure-spring-boot-starter-active-directory:3.5.0'

}

application.properties

server.port = 8080
azure.activedirectory.tenant-id = e76d7581-eabe-4e6b-a676-55e28d9325ee
azure.activedirectory.client-id = 4b37344a-6dda-4e10-9f02-359477227f25
azure.activedirectory.client-secret = SECRET_VALUE_HERE

Application.java

package pl.iterative;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
 public static void main(String[] args) {
   SpringApplication.run(Application.class, args);
 }
}

AuthController.java

package pl.iterative.api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class AuthController {
 @GetMapping("/login")
 public String login() {
   return "User logged in";
 }
}

The default setup restricts access to all application endpoints. If you wish to customize following article describes how to implement custom security configuration: https://www.baeldung.com/java-config-spring-security#Security

For the sake of this tutorial we will stick to defaults. Once the app is prepared as above config we can run it and access the resource: http://localhost:8080/api/login via a web browser.

On the first login attempt, the user is asked to consent to grant the permissions that the application requests. Always grant this consent carefully, especially to applications you don’t develop, and check precisely which exact permissions you’re granting.

Microsoft Azure permission request for identity data

A successful log-in process is finished, when user accesses the requested resource:

URL if login is successfuly

What actually happens during the authorization process is described further in the article.

Configure Nginx / Apache reverse proxy

So while we got the Spring App and Azure AD to work together it’s common scenario to put app behind a reverse proxy like Nginx or Apache.

However such case implies requirement for additional set up, as we need to properly handle headers and redirects on proxy side.

App config

Add following entries to the application.properties, as first we need to let know app that its behind the proxy.

application.properties

server.forward-headers-strategy = FRAMEWORK
server.tomcat.redirect-context-root = false

Nginx

If using Nginx as a proxy, we nned to set proxy_set_header directives and a proxy rule for /oauth2 endpoint, so roughly like following, assuming the app is accessible on localhost under port 8080:

server {
  proxy_set_header     X-Forwarded-Proto   $scheme;
  location /oauth2 {
    proxy_pass http://localhost:8080/oauth2;
  }
  location /login/oauth2 {
    proxy_pass http://localhost:8080/login/oauth2;
  }
}

Apache/Httpd

In case of Apache2/httpd similarlly, but just using a bit different directives, again assuming app availability on port 8080:

RequestHeader set X-Forwarded-Proto "https"
RewriteRule ^/oauth2/(.*) http://localhost:8080/oauth2/$1 [P]
ProxyPassReverse  /oauth2 [http://localhost:8080/oauth2/](http://localhost:8080/oauth2/)
RewriteRule ^/login/oauth2/(.*) http://localhost:8080/login/oauth2/$1 [P]
ProxyPassReverse  /login/oauth2 http://localhost:8080/login/oauth2/

These custom rewrite rules are required because these endpoints are default oauth2 endpoints to which user is redirected before and after a login attempt.

How the authorization process works

Let’s see the tab in your browser to check how the whole process works.

  1. User accessed http://localhost:8080/api/v1/login
  2. Because he was not logged-in (no appropriate cookie was sent as a request header) user is immediately redirected to authentication endpoint of your app http://localhost:8080/oauth2/authorization/azure

    Azure login form, redirected from our app endpoint

  3. This endpoint redirects user further, to login.microsoft page, along with information about which app did the redirect (this allows MS to sent back user to your app after he successfully authenticate):

    Pass of a redirect url

  4. When the user passes the appropriate email and password, it’s redirected back to the application, to endpoint http://localhost:8080/login/oauth2/code/?code=0.AV… So to the redirect-uri that we’ve set in while registering the application in Azure. The code parameter is quite important here.

    The application sends the code and client_secret in a background (via so-called backchannel - a non-frontend HTTPS call, so the user of your app does not even know about this request) to Microsoft Azure to exchange it into a JWT token that contains some user data like email address. This happens directly after the user is redirected to the redirect URL, and it’s done by the microsoft library. Which data is sent by MS Azure in the JWT token as a response, depends on the permissions that application is granted.

    Deep dive into application permissions are out of the scope of this article. You can check which permissions your app has by going to the azure console: “App registrations” -> {your_app_name} -> “API permissions”.

    The JWT token can be used to fetch more data via Microsoft Graph API (but only data to which the user gave the permission). This for example can be used to get user groups/roles in order to authorize parts of the application only for administrators or other privileged users.

    The redirect from Azure contains a response header “Set-Cookie” which indicates session initialization by the application. JSESSIONID is the id of the session. The “path=/” means that the cookie is set to all resources of the app, so the browser will append the JSESSIONID cookie to all requests under this origin (domain).

    Redirect user back to app, verify token and initalize session

  5. Finally, after all of these jumps the user is allowed to access the endpoint he requested. User accessed the page because the session cookie has been sent via request header, and based on that JSESSIONID cookie, the application authorized user access to the requested resource:

    Authentication process finished

Summary

You’ve made it till the end and now your app is secured with Azure OAuth, great work!

Securing an application with an Identity Provider can simplify the application code and shift responsibility for securing users’ data from your application to the provider. Doing it with Azure AD is one of the multiple options, but all modern Identity Providers supports OAuth2 protocol that standardizes the Authorization process.

By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.