I have a Spring GraphQL project. Each data fetcher (@SchemaMapping
) will get data from a remote API protected by authentication.
I need to propagate the authorization header from the original request (that I can see inside the @QueryMapping
method) to the data fetcher.
In the data fetcher I can use RequestContextHolder to get the request and the headers like this:
val request = (RequestContextHolder.getRequestAttributes() as ServletRequestAttributes?)?.getRequest()
val token = request?.getHeader("authorization")
This works but I am worried it could break. Spring GraphQL documentation states that:
A DataFetcher and other components invoked by GraphQL Java may not always execute on the same thread as the Spring MVC handler, for example if an asynchronous WebInterceptor or DataFetcher switches to a different thread.
I tried adding a ThreadLocalAccessor
component but it seems to me from debugging and reading source code that the restoreValue
method gets called only in a WebFlux project.
How can I be sure to get the right RequestContextHolder
in a WebMvc project?
UPDATE
I will add some code to better explain my use case.
CurrentActivity
is the parent entity while Booking
is the child entity.
I need to fetch the entities from a backend with APIs protected by authentication. I receive the auth token in the original request (the one with the graphql query).
CurrentActivityController.kt
@Controller
class CurrentActivityController @Autowired constructor(
val retrofitApiService: RetrofitApiService,
val request: HttpServletRequest
) {
@QueryMapping
fun currentActivity(graphQLContext: GraphQLContext): CurrentActivity {
// Get auth token from request.
// Can I use the injected request here?
// Or do I need to use Filter + ThreadLocalAccessor to get the token?
val token = request.getHeader("authorization")
// Can I save the token to GraphQL Context?
graphQLContext.put("AUTH_TOKEN", token)
return runBlocking {
// Authenticated API call to backend to get the CurrentActivity
return@runBlocking entityretrofitApiService.apiHandler.activitiesCurrent(mapOf("authorization" to token))
}
}
}
BookingController.kt
@Controller
class BookingController @Autowired constructor(val retrofitApiService: RetrofitApiService) {
@SchemaMapping
fun booking(
currentActivity: CurrentActivity,
graphQLContext: GraphQLContext,
): Booking? {
// Can I retrieve the token from GraphQL context?
val token: String = graphQLContext.get("AUTH_TOKEN")
return runBlocking {
// Authenticated API call to backend to get Booking entity
return@runBlocking currentActivity.currentCarBookingId?.let { currentCarBookingId ->
retrofitApiService.apiHandler.booking(
headerMap = mapOf("authorization" to token),
bookingId = currentCarBookingId
)
}
}
}
}