SAML Authentication with AngularJS and Spring Security

At Onegini we’re developing a web application with AngularJS in the front end and Spring Boot with Spring Security in the back-end. During the initial development, we were using basic authentication (username/password) for the log-in, but this doesn’t integrate well in a corporate environment. The users have an existing corporate account and expect Single Sign On for all internal applications that is done via SAML.

Spring Boot doesn’t have a module (yet) to handle SAML within Spring Security, but there’s an example project on GitHub that does the job. It contains a Java class for the Spring Security configuration. The configuration needs to be adapted to your own environment and then the basics work. However, there are a few things that are tricky if you combine it with a Single Page App (SPA) like our AngularJS front-end.

CSRF protection

The SAML flow doesn’t work if CSRF protection is enabled in the Spring Security configuration. We have solved this with two implementations of the WebSecurityConfigurerAdapter. The configuration for the "/saml/**" endpoints is without CSRF protection. The other implementation handles the other endpoints of the application and has CSRF protection enabled. You can solve this in Spring Security 4 with the following configuration:

http
    .csrf()
    .csrfTokenRepository(csrfTokenRepository())
    .ignoringAntMatchers("/saml/**")
    .and()
    .addFilterAfter(csrfHeaderFilter, CsrfFilter.class);

Log-in with redirects

Spring has an extensive tutorial about Angular JS and Spring Security, but it assumes that the user can enter their username and password in the SPA which sends the credentials to the server and gets a result. This doesn’t match the SAML flow. An unauthorised user is redirected to the Identity Provider (IdP). The IdP presents a log-in form if the user doesn’t have a valid session and after log-in the IdP redirects them back to the application (Service Provider) with a SAML assertion that contains user information.

Return to the correct URL

The default settings of the ui-router in AngularJS produces URLs like http://localhost/#/products/details/12345. Your Servlet gets a request for / and after a successful SAML authentication you are sent to http://localhost/. The location hash #/products/details/12345 is lost during the authentication. This is no issue if your users should always see the main page of the application, but in our case we wanted to support deep linking.

The solution was to enable the hmtl5mode in the $locationProvider and add a base tag to the HTML. When this feature is enabled, the URLs look like http://localhost/products/details/12345. The ServletRequest will contain the full URL and the user is redirected to the same URL after completing the authentication. This means you have to create a request mapping with Spring MVC to handle /products/details/12345. This mapping can return the same view as the root request mapping of your application.

There’s a catch: it won’t work for legacy browsers that have no support for history.pushState (MSIE 9, Opera Mini, Android 4.1).

Prevent redirects for XMLHttpRequests

Even if your application is great, people will leave their browsers for a lunch break. After the lunch break their session is lost. In a classic web application the user clicks on a link or submits a form and is redirected to the log-in page or IdP. In our SPA the action triggers an XMLHttpRequest (XHR) to exchange data or fetch a template fragment.

Spring Security will return a 302 redirect to the SAML log-in which redirects to some other page to log-in. This is not the response you expected for the request of product 12345. It’s impossible to intercept the 302 redirect on the client side so you have to prevent it on the server side.

The back-end needs to identify your request as XHR, but AngularJS doesn’t send a header for it if you use version 1.3 or up. Add the following line to the config of your AngularJS module:

$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';

Now you can identify an XHR in the back-end when this header is present.

Instead of redirecting an unauthorised XHR we respond with a “401 – Unauthorized” status by extending the SAMLEntryPoint class that you have configured for Spring Security SAML:

public class XhrSamlEntryPoint extends SAMLEntryPoint {

  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response,
                       AuthenticationException e) throws IOException, ServletException {
    if (isXmlHttpRequest(request) && e instanceof InsufficientAuthenticationException) {
      response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
      return;
    }
    super.commence(request, response, e);
  }

  private boolean isXmlHttpRequest(HttpServletRequest request) {
    return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
  }
}

We want to show a message to the user that the session is lost and they have to log in again. The first step was to configure an interceptor in the module config:

$provide.factory('notAuthorizedInterceptor', function ($q, $rootScope) {
  return {
    responseError: function (response) {
      if (response.status === 401) {
        $rootScope.unauthorized = true;
      }
      return $q.reject(response);
    }
  }
});

$httpProvider.interceptors.push('notAuthorizedInterceptor');

If the back-end responds with a 401 status code, it sets the unauthorized variable of the $rootScope to true. Now we can show a message to the user:

<div ng-if="$root.unauthorized === true">
  <p>Please <a href="javascript:window.location.reload()">log in</a> again</p>    
</div>
<div ng-hide="$root.unauthorized === true">
  <div ui-view="main"></div>
</div>

We want the browser to reload the page by making a request to the server that is not an XHR. The $window service of AngularJS prevents a complete reload and that’s the reason why the href contains javascript:window.location.reload(). This reload results in a redirection to the SAML IdP and it brings you back to the application, but now as a logged in user. Mission accomplished.

Posted in AngularJS, Development, Javascript, Spring

iSwitched

After 5 years of using different Android phones I switched to an iPhone. In 2009 I bought an HTC Hero, which was replaced by the HTC Desire a year later. Then my former employer gave me a Samsung Galaxy S2 which was replaced by the S3 after I switched jobs. I was about to switch to the Sony Xperia Z3 but I decided to go for an iDevice: the iPhone 6.

What do I miss in iOS so far?

Going back

I miss the back button on the phone. In iOS it’s up to the application to provide a back link somewhere in the screen. Usually in the top left corner, but sometimes it’s not there. That top left corner is hard to reach with one hand and Apple has created a workaround for that: tap (not click) the home button twice and the screen goes down so you can reach this button. Adding that extra physical button would have been easier. Another difference is that the back button is only within the context of the current application while the Android back button can bring you back to the previous application if that had opened another application.

No widgets on the home screen

When I opened the S3 I saw the latest weather information on my home screen. On the next screen I had an monthly overview of my calendar. On the iPhone there are only app icons. The weather and upcoming appointments for today are in the Notification Centre. It’s not in my system yet to open the Notification Centre or the Calendar app to see what’s on my schedule for the next week/month (and yes I already forgot an appointment).

Swype

It was one of the first apps I bought, but unfortunately there’s no dictionary available for Dutch yet. Let’s hope that’s just a matter of time, because Swype supports many more languages in the Android version.

Notification LED

The S3 had a blinking LED to notify me of unread messages: dark blue for mail and SMS, light blue for WhatsApp and green for Telegram and purple for MeetUp. It kept flashing until I took action to read the message or remove the notification. The iPhone blinks shortly when a message arrives, but that’s it. I don’t have my phone on me all of the time and sometimes I don’t hear or feel it, so that blinking LED was handy.

What is better?

Integration with iTunes

Samsung, I like your devices but Kies sucks. Most of the time it starts, but not always. When it has started it may or may not recognise the phone. After sacrificing a goat to the gods of Samsung it may also finish a back up successfully. More often it failed in the back up (luckily I never needed a restore) or didn’t recognise the phone. The KiesViaWifiAgent was turning my MacBook into a heating fan. iTunes just works. It recognises the phone, makes back ups and installs iOS updates.

Touch ID

The US department of Homeland Security took prints of all my fingers when I wanted to enter the US. The Dutch government wanted 2 finger prints for my new passport.  Now Apple also has a finger print of me to unlock my phone or authorise purchases (but I have no clue what else they do with it).

Permissions

I didn’t switch because of the Apple logo. Although I’ve been a fan of Apple’s desktop OS since System 6, I’ve never been attracted to the iOS. The main reason I switched from Android to iOS are the permissions apps get. In Android it’s an all or nothing decision. For instance the Facebook app wants access to my contacts, calendar, SMS and call history (and a lot more). If you don’t want to give permission to let the app access your SMS history, then you can’t install the app. This is different in iOS, where you as user can control whether the app gets access to your contacts or calendar (SMS and call history are normally inaccessible by apps). More information is in the article iOS Has App Permissions, Too: And They’re Arguably Better Than Android’s.

Posted in Apple, Technology

Retrieve Facebook profile data in Java: Apache Oltu

In previous blogposts I explained how you can get Facebook profile data using Scribe or Spring Social for the OAuth 2 handling. In this article you can read how to get the profile using Apache Oltu (formerly known as Apache Amber).

Update: browsing through the source of Apache Oltu I found an enum for the provider endpoints which wasn’t mentioned in the documentation.

Setup

The Maven dependency for the Apache Oltu OAuth client:

<dependency>
  <groupId>org.apache.oltu.oauth2</groupId>
  <artifactId>org.apache.oltu.oauth2.client</artifactId>
  <version>0.31</version>
</dependency>

The setup for the controller:

@Controller
public class FacebookOltuAuthenticator {

  private final String clientId;
  private final String clientSecret;
  private final String applicationHost;
  private final ObjectMapper objectMapper;

  @Autowired
  public FacebookOltuAuthenticator(
      @Value("#{properties['facebook.clientId']}") 
       String clientId,
      @Value("#{properties['facebook.clientSecret']}") 
       String clientSecret,
      @Value("#{properties['application.host']}") 
       String applicationHost) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.applicationHost = applicationHost;
    this.objectMapper = new ObjectMapper();
    this.objectMapper.registerModule(new AfterburnerModule());
  }
}

Start the authentication

Scribe and Spring Social have classes that know the endpoint for the authorization. Currently Apache Oltu doesn’t have such convenience classes which means we have to provide the URL for the authorization endpoint. Apache Oltu has an enum OAuthProviderType for authorization and token endpoints of common OAuth 2 providers.

@RequestMapping("/auth/facebook")
public RedirectView startAuthentication(HttpSession session) 
    throws OAuthSystemException {
  String state = UUID.randomUUID().toString();
  session.setAttribute(OAuth.OAUTH_STATE, state);
  OAuthClientRequest oAuthClientRequest = OAuthClientRequest
      .authorizationProvider(OAuthProviderType.FACEBOOK)
      .setClientId(clientId)
      .setRedirectURI(applicationHost + "/auth/facebook/callback")
      .setParameter(OAuth.OAUTH_STATE, state)
      .buildQueryMessage();

   return new RedirectView(oAuthClientRequest.getLocationUri());
}

Handle the callback

Apache Oltu has a helper class to get the authorization response from the HttpServletRequest, which means we don’t have to define the code and state parameters in the method signature.

@RequestMapping("/auth/facebook/callback")
public RedirectView callback(HttpServletRequest request, HttpSession session) 
    throws IOException, OAuthSystemException, OAuthProblemException {
  String stateFromSession = (String) session.getAttribute(OAuth.OAUTH_STATE);
  session.removeAttribute(OAuth.OAUTH_STATE);

  OAuthAuthzResponse oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
  String stateResponse = oar.getState();

  if (StringUtils.isBlank(stateResponse) || 
       !stateResponse.equals(stateFromSession)) {
    return new RedirectView("/login");
  }

  OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
  String accessToken = getAccessToken(oar, oAuthClient);

  OAuthResourceResponse resourceResponse = 
      getFacebookProfileResponse(oAuthClient, accessToken);
  if (resourceResponse.getResponseCode() != HttpServletResponse.SC_OK) {
    return new RedirectView("/login");
  }

  String facebookUserId = getFacebookUserId(resourceResponse);
  session.setAttribute("facebookUserId", facebookUserId);

  return new RedirectView("/logged-in");
}

Retrieve the AccessToken

Apache Oltu has two different classes to parse the access token response. Facebook’s response is not fully compliant with the final version of the OAuth 2 specification, but it can be parsed using the class GitHubTokenResponse.

We have to tell Apache Oltu which URL can exchange the authorization code for an access token, because there’s no Facebook specific implementation yet.

private String getAccessToken(OAuthAuthzResponse oar, OAuthClient oAuthClient)
    throws OAuthSystemException, OAuthProblemException {
  String code = oar.getCode();
  OAuthClientRequest oAuthClientRequest = OAuthClientRequest
    .tokenProvider(OAuthProviderType.FACEBOOK)
    .setGrantType(GrantType.AUTHORIZATION_CODE)
    .setClientId(clientId)
    .setClientSecret(clientSecret)
    .setRedirectURI(applicationHost + "/auth/facebook/callback")
    .setCode(code)
    .buildQueryMessage();

  GitHubTokenResponse oAuthResponse = oAuthClient.accessToken(
    oAuthClientRequest, GitHubTokenResponse.class);
  return oAuthResponse.getAccessToken(); 
}

Get the Facebook profile data

First we get the profile as JSON:

private OAuthResourceResponse getFacebookProfileResponse(
    OAuthClient oAuthClient, String accessToken)
    throws OAuthSystemException, OAuthProblemException {
  OAuthClientRequest bearerClientRequest =
      new OAuthBearerClientRequest("https://graph.facebook.com/me")
          .setAccessToken(accessToken).buildQueryMessage();

   return oAuthClient.resource(bearerClientRequest, OAuth.HttpMethod.GET,
      OAuthResourceResponse.class);
}

Just like we did with Scribe, we use Jackson to extract the user id from the JSON response:

private String getFacebookUserId(OAuthResourceResponse resourceResponse) 
    throws IOException {
  String resourceResponseBody = resourceResponse.getBody();
  JsonNode jsonNode = objectMapper.readTree(resourceResponseBody);
  JsonNode idNode = jsonNode.get("id");
  return idNode.asText();
}

Conclusion

As you can see, a lot more knowledge of the OAuth 2 flow and the specific endpoints are is needed to make this implementation with Apache Oltu. It would help developers if convenience classes are added for the larger OAuth 2 providers as Facebook, Google etc. On the other hand it does help you understand better how the flow works. When you have implemented it once, you can add any other OAuth 2 provider by changing endpoints and the way the profile is parsed from the JSON response.

Posted in Development, Geen categorie, Social Media, Technology, tools Tagged with: , , , , ,

Retrieve Facebook profile data in Java: Spring Social

In the previous blogpost I explained how you can get Facebook profile data using Scribe. This blogpost will do the same for Spring Social.

Setup

The Maven dependency for Spring Social Facebook:

<dependency>
  <groupId>org.springframework.social</groupId>
  <artifactId>spring-social-facebook</artifactId>
  <version>1.0.3.RELEASE</version>
</dependency>

Setup of the Spring controller:

@Controller
public class FacebookSpringSocialAuthenticator {
  public static final String STATE = "state";
  private String applicationHost;
  private FacebookConnectionFactory facebookConnectionFactory;

  @Autowired
  public FacebookSpringSocialAuthenticator(
      @Value("#{properties['facebook.clientId']}") 
        String clientId,
      @Value("#{properties['facebook.clientSecret']}") 
        String clientSecret,
      @Value("#{properties['application.host']}") 
        String applicationHost) {
    this.applicationHost = applicationHost;
    facebookConnectionFactory = 
      new FacebookConnectionFactory(clientId, clientSecret);
  }
}

Start the authentication

@RequestMapping("/auth/facebook")
public RedirectView startAuthentication(HttpSession session) 
    throws OAuthSystemException {
  String state = UUID.randomUUID().toString();
  session.setAttribute(STATE, state);

  OAuth2Operations oauthOperations = 
      facebookConnectionFactory.getOAuthOperations();
  OAuth2Parameters params = new OAuth2Parameters();
  params.setRedirectUri(applicationHost + "/auth/facebook/callback");
  params.setState(state);

  String authorizeUrl = oauthOperations.buildAuthorizeUrl(
      GrantType.AUTHORIZATION_CODE, params);
  return new RedirectView(authorizeUrl);
}

Handle the callback

@RequestMapping("/auth/facebook/callback")
public RedirectView callBack(@RequestParam("code") String code,
                             @RequestParam("state") String state,
                             HttpSession session) {
  String stateFromSession = (String) session.getAttribute(STATE);
  session.removeAttribute(STATE);
  if (!state.equals(stateFromSession)) {
    return new RedirectView("/login");
  }

  AccessGrant accessGrant = getAccessGrant(code);

  String facebookUserId = getFacebookUserId(accessGrant);
  session.setAttribute("facebookUserId", facebookUserId);
  return new RedirectView("/logged-in");
}

Retrieve the AccessGrant

private AccessGrant getAccessGrant(String authorizationCode) {
  OAuth2Operations oauthOperations = 
      facebookConnectionFactory.getOAuthOperations();
  return oauthOperations.exchangeForAccess(authorizationCode,
      applicationHost + "/auth/facebook/callback", null);
}

Get the Facebook user id

Spring social splits up the profile response into several classes. With Connection#fetchUserProfile you get the username, but not his identifier, which is retrieved with Connection#getKey.

private String getFacebookUserId(AccessGrant accessGrant) {
  Connection<Facebook> connection = 
      facebookConnectionFactory.createConnection(accessGrant);
  ConnectionKey connectionKey = connection.getKey();
  return connectionKey.getProviderUserId();
}

Conclusion

Spring Social has built in methods to convert the JSON response of the profile into Java objects which Scribe doesn’t do for you. Its setup is a bit simpler and there are no traces of OAuth 1 support in the Facebook classes. Spring social seems to handle the flow a bit slower (200-300 ms slower) than Scribe.

Posted in Development, Geen categorie, Social Media, Technology, tools Tagged with: , , ,

Retrieve Facebook profile data in Java: Scribe

In a series of blogposts I’ll describe how to retrieve a Facebook profile using several OAuth2 libraries for Java and Spring MVC. The first example is for Scribe.

Setup

The Maven dependency for Scribe:

<dependency>
    <groupId>org.scribe</groupId>
    <artifactId>scribe</artifactId>
    <version>1.3.5</version>
</dependency>

First you need to register an application with Facebook. Then add an HTML snippet to your site that triggers the login flow:

<a href="/auth/facebook">Get my Facebook profile</a>

We create a Spring Controller and inject the Facebook client id, client secret and the host of our application:

@Controller
public class FacebookScribeAuthenticator {

  public static final String STATE = "state";
  private String applicationHost;
  private OAuthService oAuthService;
  // Jackson ObjectMapper
  private ObjectMapper objectMapper;

  @Autowired
  public FacebookScribeAuthenticator(
      @Value("#{properties['facebook.clientId']}") 
      String clientId,
      @Value("#{properties['facebook.clientSecret']}") 
      String clientSecret,
      @Value("#{properties['application.host']}") 
      String applicationHost) {
    this.applicationHost = applicationHost;
    this.oAuthService = buildOAuthService(clientId, clientSecret);
    this.objectMapper = new ObjectMapper();
    this.objectMapper.registerModule(new AfterburnerModule());
  }
}

private OAuthService buildOAuthService(String clientId, 
                                       String clientSecret) {
  // The callback must match Site-Url in the Facebook app settings
  return new ServiceBuilder()
      .apiKey(clientId)
      .apiSecret(clientSecret)
      .callback(applicationHost + "/auth/facebook/callback")
      .provider(FacebookApi.class)
      .build();
  }
}

Start the authentication

Now we add a @RequestMapping to start the OAuth2 authorization flow. To prevent CSRF we add and store a state parameter that should be returned by Facebook. Scribe doesn’t handle this parameter in its API, so we have to add it to the authorization URL.

@RequestMapping("/auth/facebook")
public RedirectView startAuthentication(HttpSession session) 
    throws OAuthSystemException {
  String state = UUID.randomUUID().toString();
  session.setAttribute(STATE, state);
  String authorizationUrl = 
      oAuthService.getAuthorizationUrl(Token.empty()) 
        + "&" + STATE + "=" + state;
  return new RedirectView(authorizationUrl);
}

When the user goes to /auth/facebook, he is redirected to the Facebook authentication URL. If it’s his first visit, he has to approve the access to his data for your application:

Facebook approval

Handle the callback

After the user has approved the access to his profile, he is redirected to our callback endpoint with two request parameters: code and state. The code is the authorization code that can be exchanged for an access token. The state parameter should have the same value as we have sent to Facebook in the authorization request.

In case of failure we redirect the user to /login. If we can retrieve the user’s Facebook user id successfully, he is redirected to /logged-in.

@RequestMapping("/auth/facebook/callback")
public RedirectView callback(@RequestParam("code") String code,
                             @RequestParam(STATE) String state,
                             HttpSession session) 
      throws IOException {
  // Check the state parameter
  String stateFromSession = (String) session.getAttribute(STATE);
  session.removeAttribute(STATE);
  if (!state.equals(stateFromSession)) {
    return new RedirectView("/login");
  }

  // Exchange the code for an AccessToken and retrieve the profile
  Token accessToken = getAccessToken(code);
  Response response = getResponseForProfile(accessToken);
  if (!response.isSuccessful()) {
    return new RedirectView("/login");
  }

  // Store the Facebook user id in the session and redirect the user
  // to the page that needs the profile.
  String facebookUserId = getFacebookUserId(response);
  session.setAttribute("facebookUserId", facebookUserId);
  return new RedirectView("/logged-in");
}

Retrieve the AccessToken

The Token.empty() is passed because Scribe’s OAuthService handles both OAuth1 and OAuth2 providers.

private Token getAccessToken(String code) {
  Verifier verifier = new Verifier(code);
  return oAuthService.getAccessToken(Token.empty(), verifier);
}

Get the Facebook profile data

Anyone can access the public profile data for https://graph.facebook.com/<userId>, but you need to be logged in to get https://graph.facebook.com/me

private Response getResponseForProfile(Token accessToken) {
  OAuthRequest oauthRequest = 
      new OAuthRequest(Verb.GET, "https://graph.facebook.com/me");
  oAuthService.signRequest(accessToken, oauthRequest);
  return oauthRequest.send();
}

Facebook will return the profile as JSON (I know, this is not my profile):

{
 "id":"4",
 "name":"Mark Zuckerberg",
 "first_name":"Mark",
 "last_name":"Zuckerberg",
 "link":"https:\/\/www.facebook.com\/zuck",
 "username":"zuck",
 "gender":"male",
 "locale":"en_US"
}

We use Jackson to get the Facebook identifier from the JSON response:

private String getFacebookUserId(Response response) 
    throws IOException {
  String responseBody = response.getBody();
  JsonNode jsonNode = objectMapper.readTree(responseBody);
  JsonNode idNode = jsonNode.get("id");
  return idNode.asText();
}

Now you have the Facebook user id in the HttpSession. In the next blogposts I’ll explain how to do this with other Java libraries.

Posted in Development, Geen categorie, Social Media, Technology, tools Tagged with: , ,

Archives