I'm currently working on a project where we want the user to sign in via Facebook and other OAuth2 providers. Furthermore the REST api should be stateless. Therefore no cookies/jsessionids should be created/utilized. For authorization against the api, a JWT is issued by the api after a successful sign in via Facebook. The webapp consuming the rest api is build with AgularJS and satellizer. I reduced my code to a minimal example available on github.
Workflow idea:
- User enters the site, selects "login with facebook"
- The web application opens a pop up showing the facebook login page
- User signs in, accepts and facebook redirects to the webapp
- The webapp receives the token from Facebook and uses it to login to the rest api (GET /login/facebook?code=XXXXXXXXXXXXXXXXX)
- The rest api returns a JWT.
- The webapp uses the JWT to authorize against the rest api for any further requests.
Working so far
- The webapp can handle steps 1 to 4
- The rest api can handle steps 5 and 6 if you use the redirect you get from the OAuth2ClientContextFilter doing a GET to "/login/facebook" without the code parameter. (This redirect goes to facebook, you sign in, you get redirected to the api again.)
- The rest api is stateless, no jsessionid/cookies are created/required (disabled csrf and used SessionCreationPolicy.STATELESS). Except for the "login/facebook" call, this one is still creating a jsessionid.
The problem
The combination of webapp and the rest api's "login/facebook?code=XXX" call does not work. I found out that when you do a GET "login/facebook", you will be redirected to facebook with an additional state parameter attached to the url. Furthermore, this state parameter is also added when facebook redirects back to the api. From what I found online, this seems to be a kind of CSRF protection, right? And I guess this stuff is also creating the jsessionid cookie?
Questions
- Is the workflow presented above a reasonable idea?
- Do I need this CSRF protection in my use case?
- How could I disable this behavior? I mean, I used SessionCreationPolicy.STATELESS but spring still creates a session with the jsessionid. How can I create a truly stateless rest api then? (At least regarding the cookies...)
- Is this the right way to do it? Or am I missing something?
Example Code
As already mentioned, I put a complete working minimal example on GitHub. Here I will only post the (hopefully) most important part of WebSecurityConfigurerAdapter. The complete file is here.
@EnableOAuth2Client
@Configuration
public class OAuth2ClientConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private OAuth2ClientContext oAuth2ClientContext;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).enableSessionUrlRewriting(false).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/login/**").permitAll()
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()).and()
.addFilterBefore(statelessJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(createSsoFilter(facebook(), facebookSuccessHandler(), "/login/facebook"), BasicAuthenticationFilter.class);
}
private OAuth2ClientAuthenticationProcessingFilter createSsoFilter(ClientResourceDetails clientDetails, AuthenticationSuccessHandler successHandler, String path) {
OAuth2ClientAuthenticationProcessingFilter ssoFilter = new OAuth2ClientAuthenticationProcessingFilter(path);
ssoFilter.setAllowSessionCreation(false);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(clientDetails.getClient(), oAuth2ClientContext);
ssoFilter.setRestTemplate(restTemplate);
ssoFilter.setTokenServices(new UserInfoTokenServices(clientDetails.getResource().getUserInfoUri(), clientDetails.getClient().getClientId()));
ssoFilter.setAuthenticationSuccessHandler(successHandler);
return ssoFilter;
}
@Bean // handles the redirect to facebook
public FilterRegistrationBean oAuth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
Many thanks for your help!