Authenticate with REST API using Camel REST DSL

Published on - Updated on

In an application I’ve been working on, I have a lot of Camel routes that orchestrate jobs that need to run and asynchronous actions that need to be taken. Initially, I implemented all of these actions as simple Spring beans. This worked fine for a while, but as the application kept growing, there was a need to create more reusability and to be able to trigger some of these actions from an external client.

Some refactorings later, I exposed the bean actions as REST endpoints on the API and updated the Camel routes to use the REST DSL to call those APIs. This is where the challenge starts.

The APIs are secured using the Auth0 platform, which offers a “client_credentials” grant to enable machine-to-machine communication. The Camel REST calls failed for obvious reasons with a 401 Unauthorized status.

I searched the web for days for a mechanism to make the Camel REST DSL authenticate against an API, but I didn’t find anything that got me even close to implementing a Camel-native solution for authentication.

In the end, I decided to implement the authentication logic myself as a Processor that I configure as part of each REST DSL consumer call.

Setting up the REST call in Camel

Note that I’m using Kotlin for the implementation.

package com.brytecode.camel

import com.brytecode.camel.OAuth2Processor
import org.apache.camel.builder.RouteBuilder
import org.apache.camel.component.rest.RestConstants.REST_HTTP_QUERY
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Configuration

@Configuration
class RouteConfig(
    private val oAuth2Processor: OAuth2Processor,
    @Value("\${my-project.base-url}") private val baseUrl: String
) : RouteBuilder() {
    override fun configure() {
        restConfiguration().host(baseUrl).producerComponent("http")

        from("scheduler://some-action?delay=43200000")
            .process(oAuth2Processor)
            .setHeader(REST_HTTP_QUERY, simple("action=perform-action"))
            .to("rest:post:/action-endpoint")
    }
}

Nothing special is going on in the above code. We have a Camel scheduler component that triggers every 12 hours. Once the scheduler triggers, it calls the OAuth2Processor, which authenticates with the Client Credentials grant, and finally, we send the HTTP request using the Camel REST DSL.

Creating an OAuth2Processor class

The code below uses the Apache HttpComponents library to call the Auth0 endpoint to get an access token. After which Jackson is used to unmarshal the response into a Kotlin data class. This class has a single property accessToken. Finally, we modify the incoming Camel Exchange and set the “Authorization” header, which the subsequent REST API call will use.

Finally, the ApiConfigurationProperties class is a simple data class containing three properties:

In my case, I use Spring Boot @ConfigurationProperties to load these values from my application.properties file. Since storing credentials in source control is not recommended, I use Spring Cloud GCP‘s Secret Manager integration to inject the secrets in my Application Context at start-up time.

package com.brytecode.camel

import com.brytecode.camel.config.ApiConfigurationProperties
import com.brytecode.camel.config.OAuthAuthenticationFailedException
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.camel.Exchange
import org.apache.camel.Processor
import org.apache.http.HttpHeaders
import org.apache.http.HttpStatus
import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.apache.http.util.EntityUtils
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@Component
class OAuth2Processor(
    private val apiConfigurationProperties: ApiConfigurationProperties,
    private val httpClient: HttpClient,
    private val objectMapper: ObjectMapper,
    @Value("\${auth0.audience}") private val audience: String
): Processor {
    override fun process(exchange: Exchange) {
        val accessTokenRequest = HttpPost(apiConfigurationProperties.tokenUrl)
        accessTokenRequest.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.mimeType)
        accessTokenRequest.entity = StringEntity("grant_type=client_credentials&client_id=${apiConfigurationProperties.clientId}&client_secret=${apiConfigurationProperties.clientSecret}&audience=$audience")

        val response = httpClient.execute(accessTokenRequest)

        if (response.statusLine.statusCode == HttpStatus.SC_OK) {
            try {
                val accessToken = objectMapper.readValue(response.entity.content, OAuth2AccessToken::class.java)
                exchange.message.setHeader(HttpHeaders.AUTHORIZATION, "Bearer ${accessToken.accessToken}")
            } finally {
                EntityUtils.consumeQuietly(response.entity)
            }
        } else {
            throw OAuthAuthenticationFailedException("The authorization server returned the status ${response.statusLine}")
        }
    }
}