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.