Angular Authentication Complete Guide
Angular Authentication Complete Guide
Angular Authentication Complete Guide
If you use another server platform, it's just a matter of choosing a JWT
library for your platform at jwt.io, and with it, all the concepts will still
apply.
Table of Contents
In this post we will cover the following topics:
A JWT can contain any payload in general, but the most common use
case is to use the payload to define a user session.
The key thing about JWTs is that in order to confirm if they are valid, we
only need to inspect the token itself and validate the signature, without
having to contact a separate server for that, or keeping the tokens in
memory or in the database between requests.
If JWTs are used for Authentication, they will contain at least a user ID
and an expiration timestamp.
If you would like to know all the details about the JWT format in-depth
including how the most common signature types work, have a look at
this post JWT: The Complete Guide to JSON Web Tokens.
If you are curious to know what a JWT looks like, here is an example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzNTM0NTQzN
TQzNTQzNTM0NTMiLCJleHAiOjE1MDQ2OTkyNTZ9.zG-
2FvGegujxoLWwIQfNB5IT46D-xC4e8dEDYwi6aRM
You might be thinking: this does not look like JSON! Where is the JSON
then?
To see it, let's head over to jwt.io and paste the complete JWT string into
the validation tool, we will then see the JSON Payload:
1
2 {
3 "sub": "353454354354353453",
4 "exp": 1504699256
5 }
6
The sub property contains the user identifier, and the exp property
contains the expiration timestamp. This type of token is known as a
Bearer Token, meaning that it identifies the user that owns it, and
defines a user session.
If you would like to learn further about JWTs, have a look here. For the
remainder of this post, we will assume that a JWT is a string containing
a verifiable JSON payload, which defines a user session.
The separately hosted login page can have minimal Javascript or even
none at all, and it could be styled to make it look and feel as part of the
whole application.
But still, logging in a user via a login screen inside our application is also
a viable and commonly used solution, so let's cover that too.
1
2 @Component({
3 selector: 'login',
4 template: `
5 <form [formGroup]="form">
6 <fieldset>
7 <legend>Login</legend>
8 <div class="form-field">
9 <label>Email:</label>
10 <input name="email" formControlName="email">
11 </div>
12 <div class="form-field">
13 <label>Password:</label>
14 <input name="password" formControlName="password"
15 type="password">
16 </div>
17 </fieldset>
18 <div class="form-buttons">
19 <button class="button button-primary"
20 (click)="login()">Login</button>
21 </div>
22 </form>`})
33 });
34 }
35
36 login() {
37 const val = this.form.value;
38
39 if (val.email && val.password) {
40 this.authService.login(val.email, val.password)
41 .subscribe(
42 () => {
43 console.log("User is logged in");
44 this.router.navigateByUrl('/');
45 }
46 );
47 }
48 }
49 }
50
As we can see, this page would be a simple form with two fields: the
email and the password. When the user clicks the Login button, the user
and password are then sent to a client-side Authentication service via a
login() call.
Inside this service, we will either use some Javascript API for calling a
third-party service, or the Angular HTTP Client directly for doing an HTTP
POST call.
In both cases, the goal is the same: to get the user and password
combination across the network to the Authentication server via a POST
request, so that the password can be validated and the session initiated.
Here is how we would build the HTTP POST ourselves using the Angular
HTTP Client:
1
2 @Injectable()
3 export class AuthService {
4
5 constructor(private http: HttpClient) {
6 }
7
8 login(email:string, password:string ) {
9 return this.http.post<User>('/api/login', {email, password})
10 // this is just the HTTP call,
11 // we still need to handle the reception of the token
12 .shareReplay();
13 }
14 }
15
16
Before processing the login response, let's first follow the flow of the
request and see what happens on the server.
The token should then be signed and sent back to the user browser! The
key part is the JWT digital signature: that is the only thing that prevents
an attacker from forging session tokens.
This is what the code looks like for creating a new JWT session token,
using Express and the node package node-jsonwebtoken:
1 import {Request, Response} from "express";
2 import * as express from 'express';
3 const bodyParser = require('body-parser');
14
15 const RSA_PRIVATE_KEY = fs.readFileSync('./demos/private.key');
16
17 export function loginRoute(req: Request, res: Response) {
18
19 const email = req.body.email,
20 password = req.body.password;
21
22 if (validateEmailAndPassword()) {
23 const userId = findUserIdForEmail(email);
24
25 const jwtBearerToken = jwt.sign({}, RSA_PRIVATE_KEY, {
26 algorithm: 'RS256',
27 expiresIn: 120,
28 subject: userId
29 }
30
31 // send the JWT back to the user
32 // TODO - multiple options available
33 }
34 else {
35 // send status 401 Unauthorized
36 res.sendStatus(401);
37 }
38 }
There is a lot going on in this code, so we will break it down line by line:
We start by creating an Express appplication
Inside the loginRoute method we have some code that shows how the
login route can be implemented:
You can read all about the advantages of using this type of signatures in
the JWT Guide, if you would like to know how to manually reproduce
them.
the two keys are not interchangeable: they can either only
sign tokens, or only validate them, but neither key can do both
things
Why RS256?
Why use public key crypto to sign JWTs? Here are some examples of
both security and operational advantages:
we only have to deploy the private signing key in the
Authentication Server, and not on the multiple Application
servers that use the same Authentication Server
This last part is a great feature: being able to publish the validating key
gives us built-in key rotation and revocation, and we will implement that
in this post!
RS256 vs HS256
Another commonly used signature is HS256, that does not have these
advantages.
HS256 is still commonly used, but for example providers such as Auth0
are now using RS256 by default. If you would like to learn more about
HS256, RS256 and JWT signatures in general, have a look at this post.
That data could be anything such as for example the user preferred
language, but it can also contain a user identification token such as for
example a JWT.
So we can for example, store a JWT in a cookie! Let's then talk about the
advantages and disadvantages of using cookies to store JWTs, when
compared to other methods.
This means that if we store the JWT in a cookie, we will not need any
further client logic for sending back the cookie to the application server
with each request, assuming the login page and the application share
the same root domain.
Let's then store our JWT in a cookie, and see what happens. Here is how
we would finish the implementation of our login route, by sending the
JWT back to the browser in a cookie:
1
2 ... continuing the implementation of the Express login route
3
4 // this is the session token we created above
6
7 // set it in an HTTP Only + Secure Cookie
8 res.cookie("SESSIONID", jwtBearerToken, {httpOnly:true, secure:true});
9
10
Besides setting a cookie with the JWT value, we also set a couple of
security properties that we are going to cover next.
A Cookie can be marked as Secure, meaning that the browser will only
append the cookie to the request if it's being made over an HTTPS
connection.
A Cookie can also be marked as Http Only, meaning that it's not
accessible by the Javascript code at all! Note that the browser will still
append the cookie to each request sent back to the server, just like with
any other cookie.
This means for example that in order to delete a HTTP Only cookie, we
need to send a request to the server, like for example to logout the user.
Advantages of HTTP Only cookies
One advantage of an HTTP Only cookie is that if the application suffers,
for example, a script injection attack (or XSS), the Http Only flag would
still, in this disastreous scenario, prevent the attacker from getting
access to the cookie and use it to impersonate the user.
The two flags Secure and Http Only can and are often used together for
maximum security, which might make us think that Cookies are the ideal
place for storing a JWT.
But Cookies have some disadvantages too, so let's talk about those: this
will help us decide if storing cookies in a JWT is a good approach for our
application.
And if you were logged into the site this means the Cookie
containing our JWT bearer token will be forwarded too, this is
done automatically by the browser
We will cover in detail this attack in a future post, right now it's
important to realize that if we choose to store our JWT in a cookie then
we need to also put in place some defenses against XSRF.
The good news is that all major frameworks come with defenses that
can be easily put in place against XSRF, as it's such a well-known
vulnerability.
Like it happens many times, there is a design tradeoff going on here with
Cookies: using them means leveraging HTTP Only which is a great
defense against script injection, but on the other hand, it introduces a
new problem - XSRF.
that page sets an HTTP Only and Secure Cookie containing the
JWT, giving us good protection against many types of XSS
attacks that rely on stealing user identity
Plus we need to add some XSRF defenses, but there are well-
understood solutions for that
the Application code never accesses the session JWT, only the
browser
If your application falls into that case or if you are looking for
alternatives that don't rely on cookies, let's go back to the drawing board
and find what else we can do.
Not only do we want to send back the JWT itself, but it's better to send
also the expiration timestamp as a separate property.
It's true that the expiration timestamp is also available inside the JWT,
but we want to make it simple for the client to obtain the session
duration without having to install a JWT library just for that.
Here is how we can send the JWT back to the client in the HTTP
response body:
1
2 ... continuing the implementation of the Express login route
3
4 // this is the session token we created above
6
7 // set it in the HTTP Response body
8 res.status(200).json({
9 idToken: jwtBearerToken,
10 expiresIn: ...
11 });
12
13
And with this, the client will receive both the JWT and its expiration
timestamp.
But this also means that we will have to add some client code to handle
the token, because the browser will no longer forward it to the
application server with each request.
This also means that the JWT token is now readable by an attacker in
case of a successful script injection attack, while with the HTTP Only
cookie that was not possible.
Let's then continue following the journey of our JWT Bearer Token. Since
we are sending the JWT back to the client in the request body, we will
need to read it and handle it.
Step - Storing and using the JWT on the
client side
Once we receive the JWT on the client, we need to store it somewhere,
otherwise, it will be lost if we refresh the browser and would have to log
in again.
There are many places where we could save the JWT (other than
cookies). A practical place to store the JWT is on Local Storage, which is
a key/value store for string values that is ideal for storing a small
amount of data.
Note that Local Storage has a synchronous API. Let's have a look at an
implementation of the login/logout logic using Local Storage:
1
2 import * as moment from "moment";
3
4 @Injectable()
5 export class AuthService {
6
7 constructor(private http: HttpClient) {
8
9 }
10
11 login(email:string, password:string ) {
12 return this.http.post<User>('/api/login', {email, password})
13 .do(res => this.setSession)
14 .shareReplay();
15 }
16
17 private setSession(authResult) {
22 }
23
24 logout() {
25 localStorage.removeItem("id_token");
26 localStorage.removeItem("expires_at");
27 }
28
29 public isLoggedIn() {
30 return moment().isBefore(this.getExpiration());
31 }
32
33 isLoggedOut() {
34 return !this.isLoggedIn();
35 }
36
37 getExpiration() {
40 return moment(expiresAt);
41 }
42 }
43
44
For example, the client application needs to know if the user is logged in
or logged out, in order to decide if certain UI elements such as the Login
/ Logout menu buttons should be displayed or not.
Let's see how we are going to use it to tell the Application server that a
given HTTP request belongs to a given user, which is the whole point of
the Authentication solution.
Here is what we need to do: we need with each HTTP request sent to the
Application server, to somehow also append the JWT!
The application server is then going to validate the request and link it to
a user, simply by inspecting the JWT, checking its signature and reading
the user identifier from the payload.
1
2 @Injectable()
4
5 intercept(req: HttpRequest<any>,
7
8 const idToken = localStorage.getItem("id_token");
9
10 if (idToken) {
12 headers: req.headers.set("Authorization",
13 "Bearer " + idToken)
14 });
15
16 return next.handle(cloned);
17 }
18 else {
19 return next.handle(req);
20 }
21 }
22 }
23
Let's then break down how this code works line by line:
And with this in place, the JWT that was initially created on the
Authentication server, is now being sent with each request to the
Application server.
Let's then see how will the Application server use the JWT to identify the
user.
We don't want to apply this logic to all our backend routes because
certain routes are publicly accessible to all users. For example, if we
built our own login and signup routes, then those routes should be
accessible by any user.
1
2 import * as express from 'express';
3
4 const app: Application = express();
5
6 //... define checkIfAuthenticated middleware
7
8 // check if user authenticated only in certain routes
9 app.route('/api/lessons')
10 .get(checkIfAuthenticated, readAllLessons);
11
The middleware needs to throw an error also in the case that a JWT is
present, correctly signed but expired. Note that all this logic is the same
in any application that uses JWT-based Authentication.
Let's start by assuming that we had first installed the public signature
validation key in the file system of the server. Here is how we could use
it to validate JWTs:
1
2 const expressJwt = require('express-jwt');
3
4 const RSA_PUBLIC_KEY = fs.readFileSync('./demos/public.key');
5
6 const checkIfAuthenticated = expressJwt({
7 secret: RSA_PUBLIC_KEY
8 });
9
10 app.route('/api/lessons')
11 .get(checkIfAuthenticated, readAllLessons);
12
this key can only be used to validate existing JWTs, and not to
create and sign new ones
we passed the public key to express-jwt , and we got back a
ready to use middleware function!
Imagine that the server had several running instances: replacing the
public key everywhere at the same time would be problematic.
This give us a lot of benefits, such as for example simplified key rotation
and revocation. If we need a new key pair, we just have to publish a new
public key.
Typically during periodic key rotation, we will have the two keys
published and active for a period of time larger than the session
duration, in order not to interrupt user experience, while a revocation
might be effective much faster.
There is no danger that the attacker could leverage the public key. The
only thing that an attacker can do with the public key is to validate
signatures of existing JWTs, which is of no use for the attacker.
There is no way that the attacker could use the public key to forge newly
create JWTs, or somehow use the public key to guess the value of the
private signing key.
The output of this type of endpoint is a bit scary, but the good news is
that we won't have to consume directly this format, as this will be
consumed transparently by a library:
1 {
2 "keys": [
3 {
4 "alg": "RS256",
5 "kty": "RSA",
6 "use": "sig",
7 "x5c": [
8 "MIIDJTCCAg2gAwIBAgIJUP6A\/iwWqvedMA0GCSqGSIb3DQEBCwUAMDAxLjAsBgNVB
9 ],
10 "n": "wUvZ-4dkT2nTfCDIwyH9K0tH4qYMGcW_KDYeh-TjBdASUS9cd741C0XMvmVSYGR
11 "e": "AQAB",
12 "kid": "QzY0NjREMjkyQTI4RTU2RkE4MUJBRDExNzY1MUY1N0I4QjFCODlBOQ",
13 "x5t": "QzY0NjREMjkyQTI4RTU2RkE4MUJBRDExNzY1MUY1N0I4QjFCODlBOQ"
14 }
15 ]
16 }
17
A couple of details about this format: kid stands for Key Identifier, and
the x5c property is the public key itself (its the x509 certificate chain).
And that is exactly what the node-jwks-rsa library will allow us to do!
Let's have a look at this library in action:
1
2 const jwksRsa = require('jwks-rsa');
4
5 const checkIfAuthenticated = expressJwt({
6 secret: jwksRsa.expressJwtSecret({
7 cache: true,
8 rateLimit: true,
9 jwksUri: "https://angularuniv-security-course.auth0.com/.well-known
10 }),
11 algorithms: ['RS256']
12 });
13
14 app.route('/api/lessons')
15 .get(checkIfAuthenticated, readAllLessons);
16
This library will read the public key via the URL specified in property
jwksUri , and use it to validate JWT signatures. All we have to do is
The rateLimit property is also enabled, to make sure the library will not
make more then 10 requests per minute to the server containing the
public key.
This would bring the Application server to a halt very quickly so its great
to have built-in defenses against that! If you would like to change these
default parameters, have a look at the library docs for further details.
And with this, we have completed the JWT journey through the network!
We have shown how the client can use the JWT and send it
back to the server with each HTTP request
we have shown how the Application server can validate the
JWT, and link each request to a given user