I have a web application (Spring Boot + Angular). The backend authentication is implemented using Keycloak (JWT).
I need to create a GraphQL subscription using Apollo and the subscription should be authenticated/authorized. The subscription is using websocket and regrettably it is impossible to add authorization headers to the initial HTTP request. There are two ways in which the websocket can be authenticated: 1) insert the JWT token into the request URI, and 2) add token to the init message.I would prefer to use the second way as I do not want the token to be stored in any logs.
The request is being created with this code:
const auth = this.injector.get(AuthenticationService);
connections
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://localhost:9090/web-service/graphql_ws',
connectionParams: {
Authorization: 'Bearer ' + auth.getLoginDataFromStorage().access_token
}
}))
const client = new ApolloClient({
cache: new InMemoryCache(),
link: ApolloLink.from([
middleware,
errorLink,
split(
// split based on operation type
({ query }) => {
const def = getMainDefinition(query)
return def.kind === 'OperationDefinition' && def.operation === 'subscription'
},
wsLink,
httpLink
)
])
,
defaultOptions: {
watchQuery: {
fetchPolicy: 'no-cache'
},
query: {
fetchPolicy: 'no-cache'
}
},
});
I have created a WebSocketGraphQlInterceptor in order to retrieve the token during the connection initialisation, however I struggle with authenticating the session using this token.
Interceptor:
@Configuration
public class SubscriptionInterceptor implements WebSocketGraphQlInterceptor
{
@Override
public Mono<Object> handleConnectionInitialization(
WebSocketSessionInfo sessionInfo, Map<String, Object> connectionInitPayload)
{
var authToken = connectionInitPayload.get("Authorization").toString();
return Mono.just(connectionInitPayload);
}
@Override
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain)
{
List<String> token = request.getHeaders().getOrEmpty("Authorization");
return chain.next(request)
.contextWrite(context -> context.put("Authorization", token.isEmpty() ? "" : token.get(0)));
}
}
Security configuration:
@Configuration
@EnableWebSecurity
@ConditionalOnProperty(value = "keycloak.enabled", matchIfMissing = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll();
}
}
How would I be able to authenticate the user using the JWT token in the interceptor?
FYI, hope help -