Hilt - OkHttp & Interceptor - How to update Singleton with auth token - kotlin

The app starts out not authenticated. When the user is logged in I need to send a auth token to my interceptor and notify my OkHttp Singleton about the change.
My OkHttp Singleton:
#Provides
#Singleton
fun provideOkHttp(interceptor: AuthInterceptor): OkHttpClient {
return OkHttpClient
.Builder()
.addInterceptor(interceptor)
.build()
}
My AuthInterceptor Singleton:
#Provides
#Singleton
fun provideAuthInterceptor(): AuthInterceptor = AuthInterceptor()
My AuthInterceptor Class: (I got it from here: https://github.com/apollographql/apollo-kotlin/issues/2030#issuecomment-596131870 not sure if I'm using it correctly)
class AuthInterceptor() : Interceptor {
// You can change authorization here
#get:Synchronized
#set:Synchronized
var tokenString: String? = null
override fun intercept(chain: Interceptor.Chain): Response {
return chain.proceed(chain.request().newBuilder()
.addHeader("Authorization", tokenString ?: "")
.build())
}
}
So I was hoping that I can simply call authInterceptor.tokenString = token in my repository before sending requests to my server but the OkHttp singleton doesn't care about that lol.
The Interceptor and OkHttp Instances gets created at app start and tokenString is and remains NULL.
Pretty new to Hilt, OkHttp and Interceptors so maybe a pretty obvious mistake.
P.S I'm using Apollo Android + MVVM + Clean Architecture
How can I pass the token to my Interceptor and OkHttp Singleton?

In my app I'm not using a singleton for the Interceptor, instead I have a class WebService that contains my OkHttpClient and ApolloClient, and an interceptor variable
Singleton:
#Singleton
#Provides
fun provideWebService(tokenStorage: TokenStorage) = WebService(tokenStorage)
WebService:
class WebService(tokenStorage: TokenStorage) {
// the Auth Interceptor
// if tokenStorage (basically encrypted shared preferences) has a token already, create an interceptor, otherwise it will be null
var interceptor: OkHttpAuthorizationInterceptor? = if (tokenStorage.activeToken != null) OkHttpAuthorizationInterceptor(tokenStorage.activeToken!!) else null
var okHttpClient = OkHttpClient.Builder().apply {
if (interceptor != null) {
this.addNetworkInterceptor(interceptor!!) // add the interceptor only if it's not null
}
}.build()
var apolloClient = ApolloClient
.Builder()
.serverUrl(Constant.ANILIST_URL)
.okHttpClient(okHttpClient) // add the OkHttpClient to ApolloClient
.build()
}
I've ran into the same problem and came up with the following solution: In my WebService class I created a method that rebuilds the clients with a new interceptor. I call this method when I need to change the token like when logging in or switching accounts
fun switchToken(token: String) {
// rebuild the OkHttpClient
okHttpClient = okHttpClient.newBuilder().apply {
networkInterceptors().removeAll {
it is OkHttpAuthorizationInterceptor // remove the interceptor from client if it already exists (useful for changing tokens)
}
interceptor = OkHttpAuthorizationInterceptor(token) // assign a new interceptor to the interceptor variable with the given token
addNetworkInterceptor(interceptor!!) // add the new interceptor to the client
}.build()
// rebuild the ApolloClient
apolloClient = apolloClient.newBuilder().okHttpClient(okHttpClient).build()
}
And this is how my interceptor looks like (same as yours more or less):
class OkHttpAuthorizationInterceptor(private val token: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response = chain.run {
proceed(
request().newBuilder()
.addHeader("Authorization", token)
.build()
)
}
}
(Edit: changed the code of Singletons, as it turns out even with this method providing the Apollo/OkHttpClient as a singleton won't refresh them so injecting the WebService and accessing the clients from there, like webService.okHttpClient is the only option, unless there is a better way to do this altogether)

Related

What is the best pattern to deal with async callbacks using quarkus, mutiny, kotlin?

I want to expose a synchronous API that calls an async under the hood. The async API calls my server back when the data are available on its side (if data are not available in a reasonable amount of time my API returns a 404).
I've implemented a solution based on CompletableFuture, prototyped another one using an Emitter that I store in a context that can be retrieved when the incoming callback arrives but I'm not sure to do it the right way (most simple?).
What do you think about?
Thanks in advance
Patrice
#Path("/status")
class DeviceStatusController(
private val deviceStatusService: DeviceStatusService
) {
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
fun getStatus(#Valid #NotNull requestStatus: RequestStatus): Uni<String> {
return deviceStatusService.getStatus(requestStatus)
}
}
#ApplicationScoped
#Path("/notifications")
class NotificationController(val service: DeviceStatusService) {
#POST
fun receive(#RequestBody #Valid notification: Notification) {
service.receive(notification)
}
}
#ApplicationScoped
class DeviceStatusService {
private val logger: Logger = Logger.getLogger(this.javaClass.canonicalName)
data class UniContext(val emitter: CompletableFuture<String>)
//Todo: Change for redis registration with TTL
val notificationMap = mutableMapOf<String, UniContext>()
fun getStatus(requestStatus: RequestStatus): Uni<String> {
// Call async service here ... it will call us back on notifications endpoint
// It will just reply ... your request is accepted
val emitter = CompletableFuture<String>()
notificationMap[requestStatus.device] = UniContext(emitter)
return Uni.createFrom().completionStage(emitter.minimalCompletionStage())
.ifNoItem().after(Duration.ofSeconds(60))
.failWith {
cleanContexts(requestStatus.device)
TimeoutException("No reply for ${requestStatus.device}")
}
}
fun cleanContexts(id: String) {
notificationMap.remove(id)
}
fun receive(notification: Notification) {
logger.fine("Notification received for ${notification.device}")
notificationMap[notification.device]?.let {
it.emitter.complete(notification.status)
notificationMap.remove(notification.device)
}
}
}
The full sample is in my github repo

Spring Security and Token Catching

I'm currently developing an Integration Platform, but having some issues finding an idiotmatic way to set up the Spring Security Filter Chain.
Spring Security is used in 2 ways.
To authenticate the user of the application. (we'll call this the primary client registration)
To allow the user to authenticate with an external system.
In the second case, we saved their access/refresh tokens and use those later to interact with the external system(s) APIs and sync data.
When the user authenticated with the external system, the new token will overwrite the Security Context.
So far, the best solution we could come up with was overriding the SecurityContext Strategy.
class CompositeSecurityContextHolderStrategy(
val primaryClientRegistrationId: String = "primary"
) : SecurityContextHolderStrategy {
companion object {
val contextHolder = ThreadLocal<CompositeSecurityContext>()
}
override fun createEmptyContext(): SecurityContext = CompositeSecurityContext()
override fun clearContext() {
contextHolder.remove()
}
override fun getContext(): CompositeSecurityContext {
if (contextHolder.get() == null) {
contextHolder.set(createEmptyContext() as CompositeSecurityContext)
}
return contextHolder.get()
}
override fun setContext(context: SecurityContext) {
val currentContext = getContext()
val authentication = context.authentication
if (authentication is OAuth2AuthenticationToken) {
if (authentication.authorizedClientRegistrationId == primaryClientRegistrationId) currentContext.authentication = authentication
else currentContext.addAssociateAuthentication(authentication)
} else {
currentContext.authentication = context.authentication
}
}
}
class CompositeSecurityContext : SecurityContextImpl() {
val associatedAuthentications = mutableMapOf<String, Authentication>()
fun addAssociateAuthentication(authentication: Authentication) {
if (authentication is OAuth2AuthenticationToken) associatedAuthentications[authentication.authorizedClientRegistrationId] = authentication
}
fun retrieveAssociatedAuthentication(clientRegistrationId: String): Authentication? = associatedAuthentications[clientRegistrationId]
}
SecurityContextHolder.setContextHolderStrategy(CompositeSecurityContextHolderStrategy("primary"))
This works as intended. The Primary session is never overrwritten and associated token is available for consumption when our custom implementation of OAuth2AuthorizedClientService is invoked.
However, it still feels like a hack, but cannot find a better way to achieve authentication with token retrieval, without overwriting the security context.
I've seen a few older posts reference overriding OAuth2ClientAuthenticationProcessingFilter, but that is now deprecated in newer versions of SpringSecurity 5.
Any help will be appreciated.

Ktor - kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed

I am receiving back this kotlinx.coroutines.channels.ClosedReceiveChannelException upon about 50% of my api calls to a post url through Ktor HttpClient.
Our code looks like
Module.kt
bind<ServiceClient>() with singleton {
val client = HttpClient(CIO) {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
}
ServiceClient( client, instance() )
}
and our implementation of the call is
suspend fun post(request: RequestObject): List<ResponseObjects> =
client.post(endpoint) {
contentType(ContentType.Application.Json)
body = request
}
Sometimes I am able to receive back the expected results and other times I get the client closed exception. What am I doing wrong here?
I am on Ktor version 1.6.4

Spring Oauth 2.3.8 clashes with Spring security 5.4.6

I'm using a spring security in my app. I need it to secure two api's endpoints. Here is security config:
#Configuration
#EnableWebSecurity
class SecurityConfig(
private val authProps: AuthenticationProperties
) : WebSecurityConfigurerAdapter() {
override fun configure(auth: AuthenticationManagerBuilder) {
val encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
auth.inMemoryAuthentication()
.passwordEncoder(encoder)
.withUser(authProps.user)
.password(encoder.encode(authProps.password))
.roles("USER")
}
override fun configure(http: HttpSecurity) {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/firstapi/**", "/secondapi/**").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic()
}
}
Also, i'm integrating with an external service, that uses oauth2, here it's restTemplate config -
#Configuration
class ExternalServiceConfiguration {
#Bean("externalProperties")
#ConfigurationProperties("http.external.api")
fun externalHttpClientConfig() = HttpClientProperties()
#Bean("externalDetails")
#ConfigurationProperties("http.external.api.security.oauth2")
fun externalOAuth2Details() = ResourceOwnerPasswordResourceDetails()
#Bean("externalRestTemplate")
fun externalClientRestTemplate(
#Qualifier("externalProperties") externalHttpClientProperties: HttpClientProperties,
#Qualifier("externalDetails") externalOAuth2Details: ResourceOwnerPasswordResourceDetails,
customizerProviders: ObjectProvider<RestTemplateCustomizer>,
objectMapper: ObjectMapper,
): RestTemplate {
val template = OAuth2RestTemplate(externalOAuth2Details).apply {
messageConverters = listOf(MappingJackson2HttpMessageConverter(objectMapper))
requestFactory = requestFactory(externalHttpClientProperties)
errorHandler = IntegrationResponseErrorHandler()
}
customizerProviders.orderedStream().forEach { it.customize(template) }
return template
}
}
Somehow spring-security-oauth clashes with spring-security. When i try to obtain a token i'm failing at lib class method:
AccessTokenProviderChain.obtainAccessToken(...)
, because instead of null i have an AnonymousAuthenticationToken authentication at context, when it calls
SecurityContextHolder.getContext().getAuthentication()
So spring security merges anonymous context somehow, i am not sure when and how, so that it affects external calls.
Could anyone help me with advice, how can i find the collision???

How to log requests in ktor http client?

I got something like this:
private val client = HttpClient {
install(JsonFeature) {
serializer = GsonSerializer()
}
install(ExpectSuccess)
}
and make request like
private fun HttpRequestBuilder.apiUrl(path: String, userId: String? = null) {
header(HttpHeaders.CacheControl, "no-cache")
url {
takeFrom(endPoint)
encodedPath = path
}
}
but I need to check request and response body, is there any way to do it? in console/in file?
You can achieve this with the Logging feature.
First add the dependency:
implementation "io.ktor:ktor-client-logging-native:$ktor_version"
Then install the feature:
private val client = HttpClient {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
}
Bonus:
If you need to have multiple HttpClient instances throughout your application and you want to reuse some of the configuration, then you can create an extension function and add the common logic in there. For example:
fun HttpClientConfig<*>.default() {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
// Add all the common configuration here.
}
And then initialize your HttpClient like this:
private val client = HttpClient {
default()
}
I ran into this as well. I switched to using the Ktor OkHttp client as I'm familiar with the logging mechanism there.
Update your pom.xml or gradle.build to include that client (copy/paste from the Ktor site) and also add the OkHttp Logging Interceptor (again, copy/paste from that site). Current version is 3.12.0.
Now configure the client with
val client = HttpClient(OkHttp) {
engine {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = Level.BODY
addInterceptor(loggingInterceptor)
}
}
Regardless of which client you use or framework you are on, you can implement your own logger like so:
private val client = HttpClient {
// Other configurations...
install(Logging) {
logger = CustomHttpLogger()
level = LogLevel.BODY
}
}
Where CustomHttpLogger is any class that implements the ktor Logger interface, like so:
import io.ktor.client.features.logging.Logger
class CustomHttpLogger(): Logger {
override fun log(message: String) {
Log.d("loggerTag", message) // Or whatever logging system you want here
}
}
You can read more about the Logger interface in the documentation here or in the source code here
It looks like we should handle the response in HttpReceivePipeline. We could clone the origin response and use it for logging purpose:
scope.receivePipeline.intercept(HttpReceivePipeline.Before) { response ->
val (loggingContent, responseContent) = response.content.split(scope)
launch {
val callForLog = DelegatedCall(loggingContent, context, scope, shouldClose = false)
....
}
...
}
The example implementation could be found here: https://github.com/ktorio/ktor/blob/00369bf3e41e91d366279fce57b8f4c97f927fd4/ktor-client/ktor-client-core/src/io/ktor/client/features/observer/ResponseObserver.kt
and would be available in next minor release as a client feature.
btw: we could implement the same scheme for the request.
A custom structured log can be created with the HttpSend plugin
Ktor 2.x:
client.plugin(HttpSend).intercept { request ->
val call = execute(request)
val response = call.response
val durationMillis = response.responseTime.timestamp - response.requestTime.timestamp
Log.i("NETWORK", "[${response.status.value}] ${request.url.build()} ($durationMillis ms)")
call
}
Ktor 1.x:
client.config {
install(HttpSend) {
intercept { call, _ ->
val request = call.request
val response = call.response
val durationMillis = response.responseTime.timestamp - response.requestTime.timestamp
Log.i("NETWORK", "[${response.status.value}] ${request.url} ($durationMillis ms)")
call
}
}
}
Check out Kotlin Logging, https://github.com/MicroUtils/kotlin-logging it isused by a lot of open source frameworks and takes care of all the prety printing.
You can use it simply like this:
private val logger = KotlinLogging.logger { }
logger.info { "MYLOGGER INFO" }
logger.warn { "MYLOGGER WARNING" }
logger.error { "MYLOGGER ERROR" }
This will print the messages on the console.