Authenticate your operations
Authentication is not included in the GraphQL specification. This page aims at giving some guidance for the most common scenarios but doesn't pretend to be exhaustive.
Authenticating your HTTP requests with OkHttp
OkHttp Interceptors are an easy way to add an "Authorization"
header to your HTTP requests.
OkHttp Interceptors have been around for a long time and work well but only work on Android and the JVM.
Authenticating your HTTP requests with Apollo HttpInterceptor
In order to authenticate your HTTP requests in a multi-platform way, you can use an Apollo HttpInterceptor.
HttpInterceptor is multiplatform and uses an API very similar to OkHttp's:
1class AuthorizationInterceptor() : HttpInterceptor {
2 private val mutex = Mutex()
3
4 override suspend fun intercept(request: HttpRequest, chain: HttpInterceptorChain): HttpResponse {
5 var token = mutex.withLock {
6 // get current token
7 }
8
9 val response = chain.proceed(request.newBuilder().addHeader("Authorization", "Bearer $token").build())
10
11 return if (response.statusCode == 401) {
12 token = mutex.withLock {
13 // get new token
14 }
15 chain.proceed(request.newBuilder().addHeader("Authorization", "Bearer $token").build())
16 } else {
17 response
18 }
19 }
20}
For a more advanced example, you can take a look at the AuthorizationInterceptor integration tests
Authenticating your WebSockets
Authenticating WebSockets is typically handled with a specific connection payload:
1val apolloClient = ApolloClient.Builder()
2 .httpServerUrl("http://localhost:8080/graphql")
3 .webSocketServerUrl("http://localhost:8080/subscriptions")
4 .wsProtocol(
5 SubscriptionWsProtocol.Factory(
6 connectionPayload = {
7 // some servers use "authorization" directly
8 mapOf("authorization" to token)
9 // others require to wrap in a "headers" map
10 mapOf("headers" to mapOf("authorization" to token))
11 // others will expect different maps
12 // refer to your backend docs for the actual map to use
13 }
14 )
15 )
16 .build()
17
Alternatively, you can also send headers in the initial WebSocket handshake request:
1val apolloClient = ApolloClient.Builder()
2 .httpServerUrl("http://localhost:8080/graphql")
3 .subscriptionNetworkTransport(
4 WebSocketNetworkTransport.Builder()
5 .serverUrl("http://localhost:8080/subscriptions")
6 .addHeader("Authorization", authorization)
7 .build()
8 )
9 .build()
Updating your WebSocket token
When using authenticated subscriptions over WebSockets, you might want to refresh your authentication token because it was invalidated or simply because it expired.
Note: if your user logged out, you might want to consider creating a new
ApolloClient
instead. This will make sure that all other state besides the WebSocket, like cache for an example, is new and not shared amongst different users.
Sometimes the websocket will emit an error that you can use in webSocketReopenWhen
to create a new WebSocket. Other times that information will come from elsewhere in your app and you will have to force disconnect the WebSocket.
First define your own Exception:
1class WebSocketReconnectException: Exception("The WebSocket needs to be reopened")
1 val apolloClient = ApolloClient.Builder()
2 .httpServerUrl("http://localhost:8080/graphql")
3 .webSocketServerUrl("http://localhost:8080/subscriptions")
4 .wsProtocol(
5 SubscriptionWsProtocol.Factory(
6 connectionPayload = {
7 // the connection_init payload
8 // This is an example. You will most likely have to tweak it for your backend
9 mapOf("Authorization" to token)
10 }
11 )
12 )
13 .webSocketReopenWhen { e, attempt ->
14 if (e is WebSocketReconnectException) {
15 true
16 } else {
17 // Another WebSocket error happened, decide what to do with it
18 // Here we're trying to reconnect at most 3 times
19 attempt < 3
20 }
21 }
22 .build()
When you need to reopen the WebSocket, call closeConnection
:
1apolloClient.subscriptionNetworkTransport.closeConnection(WebSocketReconnectException())`
The WebSocket will close and reopen with a fresh new connectionPayload
.