Technology stack growing pains

The basic technology stack of the funnel.travel server and web client is pretty much defined. The server will be running on a Java app server, the code is based on the Spring framework, jOOQ and an underlying PostgreSQL DBMS. Mainstream stuff, really. The client will be a stand-alone Angular 4 client, with Material design. A tad unexpected are the basic issues of getting a simple login to work.

CORS

Cross-origin resource sharing becomes an issue when separating the server part from the web client. In a traditional Java stack, the entire application would be served (locally) on localhost:8080. But now, the server is running on localhost:8080, while the ng client is on localhost:4200.

The key point is to enable CORS on the WebSecurityConfigurerAdapter subclass

@Override
protected void configure(final HttpSecurity http) throws Exception {
   ...
   http.cors().configurationSource(corsConfigurationSource())
   ...
}

Make sure the CorsConfiguration allows the OPTIONS method, as NG uses that as a pre-flight check on cross-origin HTTP requests.

CSRF

Once the OPTIONS request is processed ok, the next issue is “Could not verify the provided CSRF token because your session was not found.”. A surprising number of Stackoverflow answers will suggest to simply disable Cross-site request forgery checks in Spring. But we’re developing a brand-new application, and disregarding CSRF seems like the wrong approach. (If you’re already going: “But you don’t …”, maybe skip this chapter. I’m leaving it in to illustrate the learning curve.)

A first step is to tell Spring to use a cookie-based repository instead of the default HTTP session one. The cookie name ‘XSRF-TOKEN’ is exactly what Angular is looking for.

@Override
protected void configure(final HttpSecurity http) throws Exception {
    ...
    http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
    ...
}

The client-side counterpart in Angular is enabled by default, but if you’re curious google for “CookieXSRFStrategy”. Now all that is needed is to initially get a XSRF token, and then Angular will pass that on as HTTP header in subsequent requests.

Except, this doesn’t yet work because the XHR are cross-origin. First we need to update our Spring configuration

CorsConfiguration#setAllowCredentials(true)

and in Angular set

let options = new RequestOptions();
options.withCredentials = true;

to make the cross-origin HTTP requests aware of cookies (more here). The issue now is that the Angular CookieXSRFStrategy would need to read and store the XSRF cookie from the API domain, in order to set it as “X-XSRF-TOKEN” header. As the cookie is from cross-origin, Angular doesn’t have access to the cookie.

Now that the issue is to handle CSRF on cross-origin, the penny finally drops. What CSRF does is to prevent the server from regarding a request as valid based only on cookies. The approach is to require a specific HTTP header, with a value that is dynamic and verifiable on the server (thus some form of token).

Note on the side: I’ve read statements like “If your server requires a HTTP header “X-Requested-With: XMLHttpRequest” you’re already safe, because a browser will never send that header.” I disagree there, because an attacker can easily create CSRF attack using an ajax framework.

We’re planning to use OAuth2 to handle authentication, which means that our client will be sending an “Authorization: Bearer” header with every request. Here’s the HTTP header which will prevent CSRF! I’m disabling CSRF on Spring now…

@Override
protected void configure(final HttpSecurity http) throws Exception {
    ...
    http.csrf().disable()
    ... 
}

 

Login mechanism / OAuth2

(Whenever this blog mentions ‘OAuth’, we’re refering to ‘OAuth2’).

We’re heading for a token-based authentication (great post from Chris Sevilleja), in part for scalability, but also because we anticipate future clients accessing our API (both interactive and machine clients). OAuth2 seems like a natural choice (we discarded Firebase because funnel.travel will have an “all data is stored in Switzerland” option)

After getting a basic setup following tutorial like this one, the first issue is that the NG “OPTIONS” call is not authorized on /oauth/token. Spring seems to have some issues with CORS and OPTIONS calls, the main one that the Spring CORS filter is placed too late in the filter chain, such that security is applied first. As the OPTIONS call will never have an ‘Authorized’ header, it will be rejected. For now we’re using the hack described on this stackoverflow post.

While the Angular web client can technically be seen as an “application accessing server resources on behalf of a user”, the user experience of a login resulting in a OAuth2 access pop up along the lines of “funnel.travel web client is asking for permission to access funnel.travel server. Do you want to grant permission?” wouldn’t be great. So, for our Angular client we want to support the Password Credentials grant flow.

Spring’s TokenEndpoint is a bit messed up when it comes to that grant flow, because the TokenEndpoint implementation expects an authenticated principal going into the postAccessToken() method. In Authorization Code or Implicit flow, this is fine as we’d be sending the client id + secret along. But for Password Credentials, the RFC 6749 allows for a flow without providing client id + secret.

Our approach is to add a TokenEndpointAuthenticationFilter which checks if the request is a grant_type=password request from a trusted origin. There’s some additional security by obscurity, but eventually leads to adding a client authentication. From there, the TokenEndpoint will issue a valid token.

@Override 
public void configure(final AuthorizationServerSecurityConfigurer oauthServerSecurityConfig) throws Exception {
    oauthServerSecurityConfig
            .addTokenEndpointAuthenticationFilter(new PasswordGrantAuthenticationFilter(environmentProperties.getAllowedCors()));
}

One weird effect we observed: when configuring basic web security, OAuth authorization server, OAuth resource server and finally OAuth method security in separate public classes, the resulting filter chain didn’t contain our custom filter mentioned above. Moving everything into one public class (with inner static classes) automagically resolved the issue.

Where are we now, what’s next?

Developer: We have an up and running Spring + Angular stack, CORS-enabled and secured with Spring Security and OAuth2, backed by jOOQ and PostgreSQL.

Sales guy: What, after all that time all you’ve got is a login page?

Up next: looking into i18n / l10n for Angular, which at first glance seems like rather over-engineered. But this is coming from server-side thinking of .

Advertisements

AngularJS Front-end

Based on the job market and technologies sought after therein, AngularJS is the new de-facto standard for web clients. I have several years of experience with jQuery, Backbone and Handlebars, but none at all with AngularJS. Here’s a write-down of my first impression and experience. (when refering to AngularJS, I mean AngularJS 2. I’m not bothering with the 1 version)

Hello world

It’s important to go along with what the community is doing

There are many tutorials for AngularJS, and pretty much all of them use Node.js. Our webclient will eventually be hosted on an Apache HTTP server, and so initially I discarded “the whole Node.js business”. Trying to set up an AngularJS-only project, however, seems futile. It’s important to go along with what the community is doing, to ensure that you find solutions to your problems, and your own posts get replies.

So, my first steps were:

  1. Install Node.js
  2. Install Angular CLI by running
    npm install -g angular-cli
  3. Setup a new project with
    ng new webclient

This gave me a pretty rich directory structure. Running ‘ng serve’ showed that the project does work, and so I’m all set. What surprised me, is that running ‘ng build –prod’ produces a heavy-weight vendor.bundle.js (at 700+kB!). Also, for Eclipse users, make sure to add an “exclude rule” on the ‘node_modules’ directory for all validations.

Code language

One of the reasons for using Node.js along with Angular CLI is that an ng app is typically written in TypeScript (hence the .ts suffixes). In order to run TypeScript in a browser, you need to compile it. That’s what ‘ng build’ does for you (along with other things, of course).

Directory structure

As mentioned, the new AngularJS project has quite the directory structure, so here’s a quick field guide.

angular-cli.jsonDefines the project structure for ‘ng’ commands, ie. what files are located where.

Directory / File What is it for
e2e Home of end-to-end tests, written in Jasmine and using Protractor (a Node.js module)
node_modules A huge collection of Node.js modules need to run – and eventually build – the app. This is static code, ie. should be excluded from SCM, validation etc.
src Here is where the actual app code resides
 app
 app.component.css Style definition for the main app, referenced in the app.component.ts.
 app.component.html The HTML template for the main app component (referenced in app.component.ts).
 app.component.specs.ts The test file for the main app component.
 app.component.ts Defines the main app component. Once development has started, there will be more components, named something like customeraddress.component.ts. Typically the definition has a tag ‘selector’, a template (string or URL), and a style.
 app.module.ts  The ,main app module definition, ie. imports for all components and services.
 assets Images, media files etc.
 environments Lets you define environment-specific properties
 index.html The single page. Somewhere the HTML will either have an ‘ng-app’ attribute, or a tag which is referenced as ‘selector’ in a component.ts file.
 main.ts Something like the main() method of a Java app, thus loads the main app module.
 polyfill.ts Polyfill: A polyfill is a browser fallback, made in JavaScript, that allows functionality you expect to work in modern browsers to work in older browsers, e.g., to support canvas (an HTML5 feature) in older browsers. (from Stackoverflow)
 styles.css Global styles
 test.ts The main() method to start tests, using Karma.
 tsconfig.json Configuration for TS compilation, eg. ‘should a source map be generated’, ‘what is the target ES-version’
karma.conf.js Karma configuration (duh), needed for running tests (with ‘ng test’).
package.json Defines which Node.js packages are required by the app.
protractor.conf.js Protractor configuration (duh again), needed for running end-to-end tests.
tslint.json Configuration of how TypeScript should be used by defining a set of ‘rules’.

Funny enough, AngularJS provides it’s own Anatomy of the Setup Project, but there seem to some slight differences (no SystemJS, no tsconfig has moved, etc.). The above table is what I got from running ‘ng new’.

What’s next

Now that ‘Hello World’ is running (or, in the ng-world, ‘app works’), I’ll probably setup a user service to run a login, define a login component with a little username/password form, and look into how I can configure AngularJS to protect parts of a site from unauthenticated users.