0

We have 2 parts to our application.

  • The backend written in Java 8 which exposes different REST end points using Jersey 2.0.
  • The UI which is a Single Page Application built using React and other node modules.

The Web interface uses SAML 2.0 authentication supported by Okta as the Identity Provider. The backend creates the HTTP Session and sends the JSESSIONID in the cookie.

Now the UI calls the REST end-points to display data. We need to add an authentication layer to our REST API's and I had asked a separate question about that over here Authenticating rest endpoints and the UI using Okta.

My question here specifically is what can I pass from the UI as a means of authentication to these API calls because the UI is really authenticated and the HTTP session is still valid. So I shouldn't need to create a separate OAuth 2.0 token pass it to the UI, so that UI can pass that back to the backend. The OAuth 2.0 flow makes sense for an external client using our REST end points.

Update 1

This is the excerpt of my securityContext.xml that defines both the authentication schemes:

<!-- Authenticating REST APIs -->
<security:http pattern="/rest/**" use-expressions="false">
    <security:intercept-url pattern="/nltools/**" access="IS_AUTHENTICATED_FULLY" />
    <security:custom-filter before="BASIC_AUTH_FILTER" ref="authValidationFilter" />
    <security:http-basic/>
</security:http>

<!-- SAML processing endpoints -->
<security:http pattern="/saml/**" entry-point-ref="samlEntryPoint">
    <security:custom-filter before="FIRST" ref="metadataGeneratorFilter" />
    <security:custom-filter before="CSRF_FILTER" ref="samlFilter" />
    <security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter" />
</security:http>

<!-- Secured pages with SAML as entry point -->
<security:http entry-point-ref="samlEntryPoint" use-expressions="false">
    <security:csrf />
    <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
    <security:custom-filter before="FIRST" ref="metadataGeneratorFilter" />
</security:http>

<security:authentication-manager alias="authenticationManager">
    <security:authentication-provider ref="httpBasicAuthenticationProvider" />
    <!-- Register authentication manager for SAML provider -->
    <security:authentication-provider ref="samlAuthenticationProvider"/>
    <!-- Register authentication manager for administration UI --> 
    <security:authentication-provider>
        <security:user-service id="adminInterfaceService">
            <security:user name="admin" password="admin" authorities="ROLE_ADMIN"/>
        </security:user-service>
    </security:authentication-provider>
</security:authentication-manager> 

I am not sure how can I execute SecurityContextPersistenceFilter. Should I override and add that one of the filters for my /rest/** pattern?

Update 2

Here is the JS Code (React) that makes a call to the backend:

  return Promise.resolve().then(() => {
      return request.post('/rest/v1/projects')
       .send(data)
       .then((success) => {
          console.log('success!', success);
          var projectName = success.body.name;
          var projectId = success.body.id;
          
          self.props.dispatch( projectActions.addNewProject(
            projectId,
            projectName
          ));
        
          self.props.dispatch( appActions.displayGoodRequestMessage( projectName + " Saved") ); 
          self.props.dispatch( projectActions.fetchProject( projectId ) ); 
          self.props.router.push('/projects');

      }

Now JS code can choose to send all the cookies associated with this domain and that way the backend can get the JSESSION ID cookie, however, JS doesn't do that and I don't think its the right thing to do.

If on the other hand I execute https://mydomain/rest/v1/projects in the browser and as long as I am logged in, I will get results because this time when my filter checks for the valid HTTP Session, it can get the session from the request with request.getSession(false), though that is not true when the JS calls the API. It becomes completely a different user-agent.

Update 3

Per suggestion by @Vladimír Schäfer, I could change the above JS code just a bit to send cookies as .withCredentials() and authenticate with the backend without having to do anything special

  return Promise.resolve().then(() => {
      return request.post('/rest/v1/projects')
       .withCredentials()
       .send(data)
       .then((success) => {
          console.log('success!', success);
          var projectName = success.body.name;
          var projectId = success.body.id;
          
          self.props.dispatch( projectActions.addNewProject(
            projectId,
            projectName
          ));
        
          self.props.dispatch( appActions.displayGoodRequestMessage( projectName + " Saved") ); 
          self.props.dispatch( projectActions.fetchProject( projectId ) ); 
          self.props.router.push('/projects');

      }
Community
  • 1
  • 1
user320599
  • 191
  • 1
  • 11

1 Answers1

2

As long as the REST API is part of the same application which front-end uses for authentication with Spring Security, it will have access to the JSESSIONID and therefore to the context of Spring Security which contains all the information about the authenticated user. Therefore there's no need for any additional authentication mechanism.

If you execute filter SecurityContextPersistenceFilter when handling your Jersey calls, you'll be able to access the security context using:

SecurityContextHolder.getContext().getAuthentication();

The SecurityContextPersistenceFilter uses its configured repository to fetch the Authentication object and stores it in the SecurityContextHolder. Have a look at the HttpContextRepository which it uses by default. There you will find that the security context is stored is HttpSession under key SPRING_SECURITY_CONTEXT, so you can also fetch it directly.

Of course you can also use Spring Security to enforce authentication and authorization on your REST API, just like you do on the front-end - then everything will be handled for you.

Vladimír Schäfer
  • 15,375
  • 2
  • 51
  • 71
  • I have updated my question with an excerpt of my securityContext.xml. I am not sure if I understand when you meant execute SecurityContextPeristenceFilter. – user320599 Jan 21 '17 at 23:40
  • I presumed from your question that you don't authenticate your REST with Spring Security. But you are. Your REST calls are already requiring authentication, your front-end will be sending the JSESSIONID as is, and there's nothing more you need to do - unless you want to let 3rd parties outside of your own app access the REST API (then would e.g. OAuth come into play). – Vladimír Schäfer Jan 22 '17 at 00:14
  • Thanks for your input so far. I have updated my question yet again with an excerpt of the Javascript code calling the REST API. To your point, in future I do want the REST end points to be exposed outside, when I want to switch to OAUTH2. – user320599 Jan 22 '17 at 01:03
  • Just make React send the cookies. There's nothing wrong about doing that, those API requests are no different from normal calls your browser does when change from one page to another, sending the cookies has on security implications in this case. – Vladimír Schäfer Jan 22 '17 at 11:48
  • Thank you. I have one more question though. If you notice I have added `` as the security for my REST API calls. I have added a filter `authValidationFilter` to authenticate. However as part of ``, I need to give a separate `AuthenticationProvider` to the `AuthenticationManager`. I really want my filter to authenticate. Now if I don't give ``, then Spring complains that it cannot find an entry point. How can solve that? Because tomorrow I want to add OAuth and I don't want to add in `/saml/**` filter chain. – user320599 Jan 22 '17 at 18:32
  • You can always specify an entry point which simply redirects user to the home page. – Vladimír Schäfer Jan 23 '17 at 21:46