How to handle token expiration with webclient async - kotlin

60 Views Asked by At

I have a Spring Boot v3 application written in Kotlin with coroutines/suspend functions.

I have written a simple PayPalHttpClient class to call the PayPal Orders v2 API.

I have the method to create an order as below. If the token expires, it returns 401 UNAUTHORIZED. if that's the case, I set the local accessToken to null and I call again the getAccessToken() function to retrieve a new token. I don't like that for each method I have to create the same code to handle 401s errors. Also, code is recursive meaning that if the createOrder function keeps giving 401, the code deadlocks. Is there a way nicer way to handle refreshing the token in HttpClient like classes?

private lateinit var webClient: WebClient

private var payPalAccessTokenResponse: PayPalAccessTokenResponse? = null

@PostConstruct
fun init() {
    webClient = WebClient.builder()
        .baseUrl(paypalConfig.baseUrl)
        .build()
}

suspend fun getAccessToken(): PayPalAccessTokenResponse {
    val payPalAccessTokenResponse = webClient.post()
        .uri { it.path("/v1/oauth2/token").build() }
        .header(ACCEPT, APPLICATION_JSON_VALUE)
        .header(AUTHORIZATION, encodeBasicCredentials())
        .header(ACCEPT_LANGUAGE, "en_US")
        .header(CONTENT_TYPE, APPLICATION_FORM_URLENCODED_VALUE)
        .body(BodyInserters.fromFormData("grant_type", "client_credentials"))
        .retrieve()
        .awaitBody<PayPalAccessTokenResponse>()
    this.payPalAccessTokenResponse = payPalAccessTokenResponse
    return payPalAccessTokenResponse
}

suspend fun createOrder(payPalOrderRequest: PayPalOrderRequest): PayPalOrderResponse {
    return try {
        val accessToken = payPalAccessTokenResponse?.accessToken ?: getAccessToken().accessToken
        createOrderRequest(payPalOrderRequest, accessToken)
    } catch (ex: HttpClientErrorException) {
        createOrderRequest(payPalOrderRequest, getAccessToken().accessToken)
    }
}

suspend fun createOrderRequest(payPalOrderRequest: PayPalOrderRequest, authToken: String): PayPalOrderResponse {
    val body = objectMapper.writeValueAsString(payPalOrderRequest)

    return webClient
        .post()
        .uri { it.path("/v2/checkout/orders").build() }
        .header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
        .header(
            AUTHORIZATION,
            authToken
        )
        .body(BodyInserters.fromValue(body))
        .retrieve()
        .onStatus({ status -> status.value() == 401 }) { clientResponse ->
            clientResponse.bodyToMono(String::class.java).map { body ->
                payPalAccessTokenResponse = null
                HttpClientErrorException(HttpStatus.UNAUTHORIZED, body)
            }
        }
        .awaitBody<PayPalOrderResponse>()
}
0

There are 0 best solutions below