M2M authentication and authorization with Microsoft Entra ID

Inmeta
5 min readApr 17, 2024
AI generated image: Digital authentication and authorization

Microsoft Entra ID, aka AzureAd, offers a wide range of authorization and authentication solutions and is a leading identity provider. If you are reading this, it means that you have a basic understanding of authentication and authorization and already have Microsoft Entra ID. So, let’s dive into it since there is quite a bit to cover.

We will be using Kotlin and Spring Boot to build two WEB APIs. One of them will be authenticating itself (let’s call it Daemon app) to get an authorization token, then call the second API (let’s call it Protected API). Communication will be looking something like this:

Implementation and configuration consist of these four steps:
1) Microsoft Entra app registration of protected API
2) Microsoft Entra app registration of daemon application
3) Protected API configuration
4) Daemon app configuration

This article is written by Kubilay Karayilan.

Kubilay is a Full-stack Developer and Senior Consultant at Inmeta, a Norwegian IT-consultancy company where he is also the Technical Lead in the Java department.

With a BSc in Data Engineering from OsloMET, and 11 years of experience in system- and cloud development, Kubilay or «Kubi» has been involved in many projects within migration from on-prem to cloud, cloud-native solutions, and modernization. He has extensive experience with modern tools and agile methodologies such as OKRs, Scrum, micro-services, Kafka, Kubernetes, AWS and Azure.

The first step Azure Entra ID app registration of protected API

  1. Sign in to the Azure portal.

2. Make sure you’re using the directory that contains your Azure Entra ID Select the Directories + subscriptions icon in the portal toolbar.

3. Select App registrations, and then select New Registration.

4. For Name, enter a name for the application Protected API. It does not need Redirect URI and Supported account types.

5. Select Register.

6. Under Manage, select Expose an API

8. Select app roles and create app role, fill it in like the picture!

The second step is Azure Entra ID app registration of Daemon app

  1. Select App registrations, and then select New registration
  2. Enter a name for the protected API.
  3. Under Supported account types, select single tenant
  4. Do not choose redirect URI since this is non auth daemon app without user interaction.
  5. Select register and go to Certificates and Secrets and generate client secret.
  6. Navigate to Azure Entra ID home page and go to Enterpise applications and choose Daemon App
  7. Select API permissions and add permission -> My APIs and add Protected API

The third step is Protected API configuration.

  1. Add dependencies below to your API.
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("com.azure.spring:spring-cloud-azure-starter-active-directory:4.3.0")

2. Activate Azure Entra ID in the application.yml file. Client-id and app-id-uri can be found in Azure portal > App registrations > Overview

spring:
cloud:
azure:
active-directory:
enabled: true
credential:
client-id: ${AAD_CLIENT_ID}
app-id-uri: ${AAD_APP_ID_URI}

3. Add the configuration below since we do not have browser access and session

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class AadOAuth2ResourceServerSecurityConfig : AadResourceServerWebSecurityConfigurerAdapter() {
/**
* Add configuration logic as needed.
*/
@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
super.configure(http)
.http
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
}

4. Add @PreAuthorize as shown below on controller method

@GetMapping("/hello")
@PreAuthorize("hasAuthority('APPROLE_approle')")
fun someController(): ResponseEntity {
return ResponseEntity()
}

The fourth step is Daemon app configuration

  1. Add dependency
implementation("com.microsoft.azure:msal4j:1.13.0")

2. Create ConfidentialClientApplication.

class AADClientConfig {
@Bean
fun getAADClientApplication(aadClientConfiProps:AADClientConfigProperties): ConfidentialClientApplication {
val credential: IClientCredential = ClientCredentialFactory.createFromSecret(aadClientConfiProps.clientSecret)
return ConfidentialClientApplication
.builder(aadClientConfiProps.clientID, credential)
.authority(aadClientConfiProps.authority)
.build()
}
}

3. Create a service which will acquire accessToken

class AADTokenService(
val aadApplicationClient: ConfidentialClientApplication,
aadClientConfigProps: AADClientConfigProperties
) {
private val logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass())
private val scope: Set<String?> = Collections.singleton(aadClientConfigProps.appScope)

@Throws(Exception::class)
fun acquireToken(): String? {
val parameters = ClientCredentialParameters
.builder(scope)
.build()
val result: IAuthenticationResult? = try {
aadApplicationClient.acquireToken(parameters).get()
} catch (ex: Exception) {
logger.error(ex.message)
return null
}
logger.info(result.toString())
return result?.accessToken()
}
}

4. Add configuration below in application.yml file, which confidentialClientApplication will use.

  aad:
clientSecret: ${AAD_CLIENT_SECRET}
clientID: ${AAD_CLIENT_ID}
authority: ${AAD_APP_AUTHORITY}
appScope: ${AAD_APP_SCOPE}

Now we can make a call to Azure Entra ID to get access token, may look like something like this

@Service
class AADTokenService(
val aadApplicationClient: ConfidentialClientApplication, // injected spring bean
aadClientConfigProps: AADClientConfigProperties //props from yaml above
) {
private val logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass())
private val scope: Set<String?>? = Collections.singleton(aadClientConfigProps.appScope)

/** Client credential requests will by default try to look for a valid token in the
* in-memory token cache. If found, it will return this token. If a token is not found, or the
* token is not valid, it will fall back to acquiring a token from the AAD service.
*/
fun acquireToken(): String? {
val parameters = ClientCredentialParameters
.builder(scope)
.build()
val result: IAuthenticationResult? = try {
aadApplicationClient.acquireToken(parameters).get()
} catch (ex: Exception) {
logger.error(ex.message)
return null
}
return result?.accessToken()
}
}

With the token above, you can make a call to protected API with http client of your choosing just by adding it in auth header as bearer token.

Useful sources

- Azure Entra ID daemon app registration
- Azure Entra ID protected web API registration
- Azure Entra ID resource server

--

--

Inmeta

True innovation lies at the crossroads between desirability, viability and feasibility. And having fun while doing it! → www.inmeta.no