Local HTTPS development with Angular

I'm still a little hesitant to recommend any Google sponsored open source as an enterprise solution, but Angular has definitely seen some interesting adoption in enterprises. Unfortunately some of the third party supporting documentation seems a bit dated and sparse so I'm just going to drop this little guide to setting up secure local development. Now by secure, I mean using local TLS certificates so you can use HTTPS, what I don't mean to imply is that this has anything else to do with the application security of your development.

That said let's answer, "Why do you want to do that?". So especially in large enterprise applications you are going to be communicating with a whole bunch of other external services and websites, there will be single sign on SaaS providers, data services over REST or GraphQL, probably embedded widgets/trackers/thingies and some, or most, of those are going to require that they be embedded in a secure site(served over HTTPS). To see if these external resources work with your site you'll need to be serving the page/app securely or integration will fail because of mixed content. Mixed content, secure and insecure bits together, can be annoying by default, or fatal with some of the improved security features of modern browsers. And it's just going to get harder and harder to make mixed content work in a way you can repeatably test in the future as browsers get stricter with security enforcement. So, here I'm going to go over the three pretty simple steps to setup secure local development with a self signed cert in Angular.

Assuming you already have NodeJS installed, first install the Angular CLI Angular CLI

npm install -g @angular/cli

And create a default project as our base to work on.

ng new angular-base --routing=false --style=css
cd angular-base

Step 1: Generating self signed certificates

Normally I would recommend the excellent mkcert package to create a local CA and generate the certs off that local CA. But sometimes you just can't do that, so if you can find openssl on your dev system here's a way to leverage that.

Create a new plaintext file called certificate.cnf with the following contents.

[req]
default_bits = 2048
prompt = no
default_md = sha256
x509_extensions = v3_req
distinguished_name = dn

[dn]
C = US
ST = Atlanta
L = Atlanta
O = My Organisation
OU = My Organisational Unit
emailAddress = email@domain.com
CN = localhost

[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost

Then run openssl with these parameters.

openssl req -new -x509 -newkey rsa:2048 -sha256 -nodes -keyout localhost.key -days 3560 -out localhost.crt -config certificate.cnf

This parameter soup will generate two files, localhost.key and localhost.crt. The .key file is very sensitive, always considered private, and should never be shared. The .crt file is a public certificate in the x509 format and can be shared, but out of a sense of consistency for security, should never be in code.

Now add these two lines to your .gitignore file:

*.crt
*.key

In this example we are making these files inside our codebase directory for simplicity, but more practically you should put these somewhere else. Such as making a cert directory inside your home folder, and then changing the following configuration steps to point to them there. You can even reuse the "localhost" certs for any local development site you put on the hostname this way.

For teams working with self signed certs this location should be an agreed upon standard location so that the configuration doesn't need to be modified from person to person working on the project.

Step 2: Set Angular to serve locally with TLS(HTTPS protocol)

The Angular general configuration file needs to be modified to contain an options section which will contain: a boolean flag to serve SSL, a location for the certificate private key file, and a location for the public x509 certificate file.

The server configuration section is going to be in angular.json or angular-cli.json depending on your version. The changes are in the "options" sub array.

       ...
       "serve": {
         "builder": "@angular-devkit/build-angular:dev-server",
         "configurations": {
           "production": {
             "browserTarget": "angular-base:build:production"
           },
           "development": {
             "browserTarget": "angular-base:build:development"
           }
         },
         "defaultConfiguration": "development",
         "options": 
         {
           "ssl": true,
           "sslKey": "localhost.key",
           "sslCert": "localhost.crt"
         }
       },

Step 3: Set Angular test runners to run with TLS(HTTPS)

Changing your local serving to HTTPS is a great improvement, but if none of your integration tests pass then it's just not enough. So we also need to modify the testing configuration to also serve over HTTPS with our self signed certs. By default this will be in karma.conf.js for Angular CLI projects, but testing suites differ, so adjust these instructions accordingly. In general you will always need to point to the .key and the .crt file, and usually you need some sort of flag to use HTTPS.

At the top of the karma.conf.js file add this require statement below the opening comments.

// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

// Required to load the TLS certs from files
var fs = require('fs');
...

And add this set of options at the bottom (starting with httpsServerOptions):

    ...
    singleRun: false,
    restartOnFileChange: true,
    httpsServerOptions: {
      key: fs.readFileSync('localhost.key', 'utf8'),
      cert: fs.readFileSync('localhost.crt', 'utf8')
    },
    protocol: 'https'
  });
};

Conclusion

Now when you browse to your local dev build, you may be asked to add an exception to the self signed cert, to which you should "Accept the Risk" and add it (local development is really the only time this is clearly ok to do).

Headless testing options will often require a "--no-check-certificate", or "proxyValidateSSL: false", or something similar to prevent the headless browser from trying to find a CA on the internet to validate the certificates with.

And that's it. Now your local angular server should work over HTTPS (and only HTTPS), and your tests should run over HTTPS and be able to pull in external secure resources.