Integrate Microsoft Entra Id with Quarkus Backend application

Jyoti Ranjan Behera 0 Reputation points
2024-11-14T13:02:18.2633333+00:00

I'm developing a backend application in Quarkus. I want to secure my application with Microsoft Entra ID. Here my flow says I'll get a bearer token/jwt token on behalf of users then using the token, i have to access my api resources. How can I generate a jwt token and use it? For that I'm using Quarkus OIDC dependency. But I am not able to decode my token what I am using the library. I have to validate my token with signature. Here I am using the library quarkus-oidc, quarkus-rest , quarkus-kotlin, Quarkus-config-yaml.

pom.xml:

<dependencies>     <dependency>         <groupId>io.quarkus</groupId>         <artifactId>quarkus-rest</artifactId>     </dependency>     <dependency>         <groupId>io.quarkus</groupId>         <artifactId>quarkus-kotlin</artifactId>     </dependency>     <dependency>         <groupId>io.quarkus</groupId>         <artifactId>quarkus-oidc</artifactId>     </dependency>     <dependency>         <groupId>io.quarkus</groupId>         <artifactId>quarkus-config-yaml</artifactId>     </dependency>     <dependency>         <groupId>io.quarkus</groupId>         <artifactId>quarkus-arc</artifactId>     </dependency>     <dependency>

application.yml:


quarkus:
  http:
    port: 9090
  oidc:
    auth-server-url: "https://login.microsoftonline.com/73f2e714-a32e-4697-9449-dffe1df8a5d5/v2.0"
    client-id: "bcd7b88b-a646-417a-aead-2076947c617f"
    credentials:
      secret: "qP98Q~UURqtZnMSwpIP9YGh~BWDDcFY7PGeFSb.-"
    application-type: "web_app"
    token:
      refresh-expired: true
    authentication:
      redirect-path: "/"
      restore-path-after-redirect: true
      scope: "openid profile email"
    roles:
      role-claim-path: "roles"

/src/com.example.demo/auth/AuthorizationFilter.kt:

package com.example.auth

import com.example.service.AuthService
import jakarta.inject.Inject
import jakarta.ws.rs.container.ContainerRequestContext
import jakarta.ws.rs.container.ContainerRequestFilter
import jakarta.ws.rs.core.Response
import jakarta.ws.rs.ext.Provider
import org.eclipse.microprofile.jwt.JsonWebToken
import java.io.IOException

@Provider
class AuthorizationFilter : ContainerRequestFilter {

    @Inject
    lateinit var authService: AuthService

    @Inject
    lateinit var jwt: JsonWebToken

    @Throws(IOException::class)
    override fun filter(requestContext: ContainerRequestContext) {
        // Extract the Authorization header
        val authorizationHeader = requestContext.getHeaderString("Authorization")

        // If there's no Authorization header or it's invalid, respond with an error
        if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
            requestContext.abortWith(
                Response.status(Response.Status.UNAUTHORIZED)
                    .entity("Authorization header missing or invalid")
                    .build()
            )
            return
        }

        // Extract the token from the Authorization header
        val token = authorizationHeader.substring("Bearer ".length).trim()

        // Validate the token using AuthService
        val response = authService.validateToken(token, jwt)

        // If the token is invalid, stop further processing by aborting the request
        if (response.status == Response.Status.UNAUTHORIZED.statusCode) {
            requestContext.abortWith(response)
        }
    }

}

/src/com.example.demo/auth/AuthResource.kt:

package com.example.auth
import jakarta.annotation.security.RolesAllowed
import jakarta.ws.rs.GET
import jakarta.ws.rs.POST
import jakarta.ws.rs.Path
import jakarta.ws.rs.core.Response


@Path("/api")
class AuthResource {
    @POST
    @Path("/post-endpoint")
    @RolesAllowed("user")
    fun postEndpoint(body: String?): Response {
        // Your logic for POST request
        return Response.ok().build()
    }

    @POST
    @Path("/user-endpoint")
    @RolesAllowed("user")
    fun userEndpoint(body: String?): Response {
        // Logic for POST request for users
        return Response.ok().build()
    }

    @GET
    @Path("/data")
    @RolesAllowed("user")
    fun get(): Response {
        // Logic for POST request for users
        return Response.ok("hello").build()
    }
}

/src/com.example.demo/service/impl/AuthServiceImpl.kt:

package com.example.service.impl

import com.example.service.AuthService
import jakarta.inject.Singleton
import jakarta.ws.rs.core.Response
import org.eclipse.microprofile.jwt.JsonWebToken
@Singleton
class AuthServiceImpl: AuthService {


    override fun validateToken(token: String, jwt: JsonWebToken): Response {
        try {
            val token = token

            println("Received JWT Token: $token")

            // Decode and validate the token
            val userId = jwt.subject // Subject claim (usually user ID or username)
            val email = jwt.claim<String>("email") // Optional: Extract additional claims like email
            val roles = jwt.claim<List<String>>("roles") // Extract roles if available

            println("Decoded JWT Claims:")
            println("User ID: $userId")
            println("Email: $email")
            println("Roles: $roles")

            // Extract the 'iat' (issued at) claim and validate
            val issuedAtString = jwt.claim<String>("iat")?.toString()
            val issuedAt = issuedAtString?.toLongOrNull() // Convert to Long safely

            println("Issued At (iat): $issuedAt")
            // Validate the 'iat' (issued at) claim
            if (issuedAt == null) {
                return Response.status(Response.Status.UNAUTHORIZED)
                    .entity("Token not issued correctly (missing 'iat' claim)").build()
            }

            // Return success with decoded claims
            return Response.ok(mapOf("message" to "Token is valid", "userId" to userId, "email" to email, "roles" to roles)).build()
        } catch (e: Exception) {
            // Handle token validation failure (e.g., invalid or expired token)
            println("Error during token validation: ${e.message}")
            return Response.status(Response.Status.UNAUTHORIZED)
                .entity("Invalid or expired token").build()
        }
    }
}

/src/com.example.demo/service/AuthService.kt:


package com.example.service

import jakarta.ws.rs.core.Response
import org.eclipse.microprofile.jwt.JsonWebToken

interface AuthService {
    fun validateToken(token: String, jwt: JsonWebToken): Response

}

 

Here are the references that I'm following:

Kindly suggest me if I'm doing any wrong.

Microsoft Security Microsoft Entra Microsoft Entra ID
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Raja Pothuraju 23,465 Reputation points Microsoft External Staff Moderator
    2024-11-18T02:26:10.8066667+00:00

    Hello @Jyoti Ranjan Behera,

    Thank you for posting your query on Microsoft Q&A.

    Based on your description, it looks like your setup for integrating Microsoft Entra ID with a Quarkus application using the quarkus-oidc extension looks correct overall. However, there are a few areas to clarify and refine regarding token decoding and validation, especially since you’re experiencing issues with decoding the JWT token and validating its signature.

    Quarkus OIDC Configuration: Ensure your OIDC configuration in application.yml is correct. The auth-server-url, client-id, and secret must match the configuration in your Azure portal and refer the below Microsoft OIDC document to know what the parameters are required while generate an ID token.

    Using JsonWebToken: The JsonWebToken interface provided by MicroProfile JWT is injected into your filter and service, but it's important to understand how it works. When a request comes in with a valid JWT, Quarkus will automatically decode and validate it if configured correctly.

    Token Validation: You should not manually decode and validate the JWT in your AuthServiceImpl. Instead, you can rely on the JsonWebToken instance injected into your filter. The JsonWebToken will automatically represent the claims of the validated token.

    For more details, refer to the official Quarkus documentation:

    Quarkus Security OpenID Connect Client

    To generate an OIDC token for your application, refer to this guide:

    Microsoft Entra OIDC Protocols

    I hope this information is helpful. Please feel free to reach out if you have any further questions.

    If the answer is helpful, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Thanks,
    Raja Pothuraju.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.