State

Angular Authentication With JWT: The Complete Guide

This post is a step-by-step guide for both designing and implementing JWT-based Authentication in an Angular Application. The goal here is to discuss JWT-based Authentication Design and Implementation in general, by going over the multiple design options and design compromises involved, and then app

Angular Authentication With JWT: The Complete Guide — State article by Angular University on Angular In Depth
Angular Authentication With JWT: The Complete Guide — State article by Angular University on Angular In Depth
On this page · 20 sections

This guide walks through the design and implementation of JWT-based authentication in an Angular application, from initial planning to final deployment.

The focus is on the general principles of JWT-based authentication design and implementation, covering the various design choices and trade-offs involved, then applying those ideas within an Angular application.

We'll trace a JWT's complete path: starting at the authentication server where it's created, moving to the client, and then back to the application server. Along the way, we'll examine all the design options and decisions that come into play.

Since authentication requires server-side code to work, we'll include that as well to provide full context and show how all the pieces fit together.

The server examples use Node / TypeScript, which is familiar to Angular developers, but the concepts are not tied to any specific platform.

If you're using a different server technology, simply pick a JWT library for your platform from jwt.io, and all the principles discussed here will still apply.

Table of Contents

Here's what we'll cover in this post:

  • Step 1 - The Login Page
    • JWT-based Authentication in a Nutshell
    • User Login in an Angular Application
    • Why use a separately hosted Login Page?
    • Login directly in our single page application
  • Step 2 - Creating a JWT-based user Session
  • Step 3 - Sending a JWT back to the client
    • Where to store a JWT Session Token?
    • Cookies vs Local Storage
  • Step 4 - Storing and using the JWT on the client side
    • Checking User Expiration
  • Step 5 - Sending The JWT back to the server on each request
    • How to build an Authentication HTTP Interceptor
  • Step 6 - Validating User Requests
    • Building a custom Express middleware for JWT validation
    • Configuring a JWT validation middleware using express-jwt
    • Validating JWT Signatures - RS256
    • RS256 vs HS256
    • JWKS (JSON Web Key Set) endpoints and key rotation
    • Implementing JWKS key rotation using node-jwks-rsa
  • Summary and Conclusions

Let's dive right into JWT-based Angular authentication!

JWT-based User Sessions

Let's begin by explaining how JSON Web Tokens establish a user session: in essence, JWTs are digitally signed JSON payloads, encoded in a URL-friendly string format.

A JWT can carry any kind of payload, but its most common use is defining a user session through the payload.

The crucial feature of JWTs is that validating them only requires inspecting the token itself and checking the signature—there's no need to consult a separate server, maintain tokens in memory, or store them in a database between requests.

When used for authentication, JWTs typically contain at least a user ID and an expiration timestamp.

For an in-depth look at the JWT format, including how common signature types work, check out this post: JWT: The Complete Guide to JSON Web Tokens.

Curious about what a JWT actually looks like? Here's an example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzNTM0NTQzNTQzNTQzNTM0NTMiLCJleHAiOjE1MDQ2OTkyNTZ9.zG-2FvGegujxoLWwIQfNB5IT46D-xC4e8dEDYwi6aRM

You might be wondering: this doesn't look like JSON at all! So where's the JSON?

To find out, head over to jwt.io and paste the complete JWT string into the validation tool—you'll see the JSON payload displayed:

The sub property holds the user identifier, while the exp property contains the expiration timestamp. This kind of token is called a Bearer Token, meaning it identifies its owner and defines a user session.

A bearer token is a signed, temporary replacement for the username/password combination!

To learn more about JWTs, see this article. For the rest of this post, we'll treat a JWT as a string containing a verifiable JSON payload that defines a user session.

The first step in implementing JWT-based authentication is issuing a bearer token to the user, which is exactly what a Login / Sign up page is designed to do.

Step 1 - The Login Page

Authentication begins with a Login page, which can be hosted either on our own domain or on a third-party one. In enterprise settings, the login page often lives on a separate server as part of a company-wide Single Sign-On solution.

On the public internet, the login page might also be:

  • hosted by a third-party Authentication provider such as Auth0
  • available directly in our single page application via a login screen route or a modal

A separately hosted login page is a security improvement because the password never touches our application code in the first place.

That externally hosted page can have minimal JavaScript—or none at all—and can be styled to blend seamlessly with the rest of the application.

Still, logging users in directly through a login screen inside our application is also a valid and widely used approach, so let's cover that too.

Login page directly on the SPA application

If we were to build a login page directly in our SPA, it would look something like this:

As shown, it's a straightforward form with two fields: email and password. When the user clicks the Login button, those credentials are sent to a client-side Authentication service via a login() call.

Why create a separate Authentication service?

Centralizing all client-side authentication logic in a single, application-wide AuthService singleton helps keep our code organized.

That way, if we later need to switch security providers or refactor our security logic, we only have to modify this one class.

Inside this service, we'll either use some JavaScript API for calling a third-party service, or the Angular HTTP Client to make an HTTP POST request.

In both cases, the objective is the same: send the user/password combination over the network to the Authentication server via a POST request, so the password can be verified and the session initiated.

Here's how we'd build the HTTP POST ourselves using the Angular HTTP Client:

We're using shareReplay to ensure the receiver of this Observable doesn't accidentally trigger multiple POST requests due to multiple subscriptions.

Before we process the login response, let's follow the request's flow and see what happens on the server.

Step 2 - Creating a JWT Session Token

Whether we use an application-level login page or a hosted one, the server logic that handles the login POST request remains the same.

In both cases, the goal is to validate the password and establish a session. If the password is correct, the server issues a bearer token stating:

The bearer of this token is the user with the technical ID 353454354354353453, and the session is valid for the next two hours

The token must then be signed and sent back to the user's browser! The critical element here is the JWT digital signature—it's the only thing preventing an attacker from forging session tokens.

This is what the code for creating a new JWT session token looks like, using Express and the node-jsonwebtoken package:

There's quite a bit happening in this code, so let's break it down line by line:

  • We start by creating an Express appplication
  • Next, we configure the bodyParser.json() middleware so Express can read JSON payloads from the HTTP request body
  • We then define a route handler named loginRoute, which triggers when the server receives a POST request targeting the /api/login URL

Inside the loginRoute method, we have code demonstrating how the login route can be implemented:

  • Thanks to the bodyParser.json() middleware, we can access the JSON request body via req.body
  • We begin by extracting the email and password from the request body
  • Next, we validate the password to check if it's correct
  • If the password is wrong, we return HTTP status code 401 Unauthorized
  • If the password is correct, we retrieve the user's technical identifier
  • We create a plain JavaScript object with the user ID and an expiration timestamp, then send it back to the client
  • We sign the payload using the node-jsonwebtoken library, choosing the RS256 signature type (more on this shortly)
  • The .sign() call returns the JWT string itself

In summary, we've validated the password and created a JWT session token. Now that we understand how this code works, let's focus on the key part: signing the JWT containing the user session details with an RS256 signature.

Why does the signature type matter? Because without understanding it, we won't understand the application server code needed to validate this token.

What are RS256 Signatures?

RS256 is a JWT signature type based on RSA, a widely used public key encryption technology.

A major advantage of RS256 signatures is that they separate the ability to create tokens from the ability to verify them.

You can read all about the benefits of this signature type in the JWT Guide, if you'd like to learn how to reproduce them manually.

In a nutshell, RS256 signatures work like this:

  • a private key (such as RSA_PRIVATE_KEY in our code) is used for signing JWTs
  • a public key is used to validate them
  • the two keys are not interchangeable: each can only sign tokens or only validate them, but neither can do both

Why RS256?

Why use public key crypto to sign JWTs? Here are some security and operational advantages:

  • we only need to deploy the private signing key on the Authentication Server, not on the multiple Application servers that rely on it
  • we don't have to shut down the Authentication and Application servers in a coordinated way to change a shared key everywhere simultaneously
  • the public key can be published at a URL and automatically read by the Application server at startup and periodically thereafter

That last point is particularly powerful: being able to publish the validating key gives us built-in key rotation and revocation, which we'll implement in this post!

This works because enabling a new key pair only requires publishing a new public key, and we'll see that in action.

RS256 vs HS256

Another commonly used signature type is HS256, which doesn't offer these benefits.

HS256 is still widely used, but providers like Auth0 now default to RS256. If you want to learn more about HS256, RS256, and JWT signatures in general, check out this post.

Regardless of the signature type we choose, we need to send the freshly signed token back to the user's browser.

Step 3 - Sending a JWT back to the client

There are several ways to return the token to the user, for example:

  • In a Cookie
  • In the Request Body
  • In a plain HTTP Header

JWTs and Cookies

Let's start with cookies—why not use them? JWTs are sometimes mentioned as an alternative to cookies, but these are two very different concepts. Cookies are a browser data storage mechanism, a place to safely store small amounts of data.

That data could be anything, like the user's preferred language, but it can also hold a user identification token such as a JWT.

So we could, for instance, store a JWT in a cookie! Let's then discuss the advantages and disadvantages of using cookies to store JWTs compared to other methods.

How the browser handles cookies

A unique aspect of cookies is that the browser automatically appends cookies for a particular domain or sub-domain to the headers of every HTTP request.

This means that if we store the JWT in a cookie, we don't need any additional client logic to send the cookie back to the application server with each request—assuming the login page and the application share the same root domain.

Let's store our JWT in a cookie and see what happens. Here's how we'd finish our login route implementation by sending the JWT back to the browser in a cookie:

Besides setting a cookie with the JWT value, we also set several security properties that we'll cover next.

Unique security properties of Cookies - HttpOnly and Secure Flags

Another unique aspect of cookies is their security-related properties, which help ensure secure data transfer.

A cookie can be marked as Secure, meaning the browser will only append it to requests made over an HTTPS connection.

A cookie can also be marked as HttpOnly, meaning it's not accessible by JavaScript code at all! Note that the browser will still append the cookie to each request sent to the server, just like any other cookie.

This means, for example, that to delete an HttpOnly cookie, we need to send a request to the server, such as a logout request.

Advantages of HTTP Only cookies

One advantage of an HttpOnly cookie is that if the application suffers a script injection attack (or XSS), the HttpOnly flag would still—even in that disastrous scenario—prevent the attacker from accessing the cookie and using it to impersonate the user.

The Secure and HttpOnly flags are often used together for maximum security, which might make cookies seem like the ideal place to store a JWT.

But cookies have drawbacks too, so let's talk about those: this will help us decide whether storing cookies in a JWT is the right approach for our application.

Disadvantages of Cookies - XSRF

Applications with bearer tokens stored in cookies are vulnerable to Cross-Site Request Forgery, also known as XSRF or CSRF. Here's how it works:

  • someone sends you a link and you click it
  • the link sends an HTTP request to the site under attack, including all cookies associated with that site
  • if you were logged into the site, the cookie containing your JWT bearer token gets forwarded too, automatically by the browser
  • the server receives a valid JWT, making it impossible for the server to distinguish this attack from a legitimate request

This means an attacker could trick a user into performing certain actions on their behalf, simply by sending an email or posting a link in a public forum.

This attack is less powerful than it might seem, but the problem is it's very easy to execute: all it takes is an email or a social media post.

We'll cover this attack in detail in a future post; for now, it's important to understand that if we store our JWT in a cookie, we also need to implement defenses against XSRF.

The good news is that all major frameworks include easily deployable XSRF defenses, given how well-known this vulnerability is.

As often happens, there's a design trade-off with cookies: using them leverages HttpOnly, a strong defense against script injection, but it also introduces a new problem—XSRF.

Cookies and Third-Party Authentication providers

A potential problem with receiving the session JWT in a cookie is that we wouldn't be able to receive it from a third-party web domain that handles the authentication logic.

That's because an application running on app.example.com cannot access cookies from another domain like security-provider.com.

In that scenario, we wouldn't be able to access the cookie containing the JWT and send it to our server for validation, making cookies unfeasible.

Can we get the best of the two solutions?

Third-party authentication providers might let us run the externally hosted login page on a configurable subdomain of our website, such as login.example.com.

That would allow us to combine the best of all these solutions. Here's what it would look like:

  • an externally hosted login page on our own subdomain login.example.com, and an application on example.com
  • that page sets an HttpOnly and Secure cookie containing the JWT, offering solid protection against many types of XSS attacks that rely on stealing user identity
  • plus, we'd add XSRF defenses, for which well-understood solutions exist

This approach gives us maximum protection against both password and identity token theft:

  • the Application never receives the password in the first place
  • the Application code never accesses the session JWT—only the browser does
  • the application is not vulnerable to request forgery (XSRF)

This scenario is sometimes used in enterprise portals and provides excellent security. However, it depends on the security provider or enterprise security proxy supporting a custom domain for hosted login pages.

That feature (custom subdomain for hosted login) isn't always available, which would make the HttpOnly cookie approach unworkable.

If your application falls into that category, or if you're looking for alternatives that don't rely on cookies, let's go back to the drawing board and see what else we can do.

Sending the JWT back in the HTTP response body

Cookies, with their unique HttpOnly property, are a strong choice for storing JWTs, but other good options exist. For instance, instead of cookies, we can send the JWT back to the client in the HTTP response body.

In addition to the JWT itself, it's better to also send the expiration timestamp as a separate property.

While the expiration timestamp is already inside the JWT, we want to make it easy for the client to get the session duration without installing a JWT library just for that.

Here's how we can send the JWT back to the client in the HTTP response body:

And with that, the client receives both the JWT and its expiration timestamp.

Design compromises of not using Cookies for JWT storage

Not using cookies has the advantage of eliminating XSRF vulnerability, which is one benefit of this approach.

But it also means we'll need to add client code to handle the token, because the browser will no longer automatically forward it to the application server with each request.

This also means the JWT is now readable by an attacker in case of a successful script injection attack, which wasn't possible with an HttpOnly cookie.

This is a classic example of the design compromises often involved in choosing a security solution: there's usually a trade-off between security and convenience.

Let's continue following our JWT bearer token's journey. Since we're sending it back to the client in the request body, we'll need to read and handle it.

Step 4 - 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 on browser refresh, requiring the user to log in again.

There are many places to save the JWT (besides cookies). A practical option is Local Storage, a key/value store for string values, ideal for small amounts of data.

Note that Local Storage has a synchronous API. Let's look at an implementation of login/logout logic using Local Storage:

Let's break down what's happening in this implementation, starting with the login method:

  • We receive the login call result, containing the JWT and the expiresIn property, and pass it directly to the setSession method
  • inside setSession, we store the JWT directly in Local Storage under the id_token key
  • We take the current time and the expiresIn property to calculate the expiration timestamp
  • We then save that expiration timestamp as a numeric value in the expires_at Local Storage entry

Using Session Information on the client side

Now that all session information is on the client, we can use it throughout the rest of the application.

For example, the client needs to know if the user is logged in or out, to decide whether certain UI elements, like Login / Logout menu buttons, should be displayed.

This information is now available via the isLoggedIn(), isLoggedOut(), and getExpiration() methods.

Sending The JWT to the server on each request

Now that the JWT is saved in the user's browser, let's keep tracking its journey across the network.

We need to see how to use it to tell the Application server that a given HTTP request belongs to a specific user—which is the entire purpose of the authentication solution.

Here's what we need to do: with each HTTP request sent to the Application server, we must somehow also attach the JWT!

The application server will then validate the request and associate it with a user, simply by inspecting the JWT, verifying its signature, and reading the user identifier from the payload.

To guarantee every request includes a JWT, we'll use an Angular HTTP Interceptor.

How to build an Authentication HTTP Interceptor

Here's the code for an Angular Interceptor that includes the JWT with each request sent to the application server:

Let's break down how this code works line by line:

  • we first retrieve the JWT string from Local Storage directly
  • then we check if the JWT is present
  • if the JWT isn't present, the request goes through to the server unmodified
  • if the JWT is present, we clone the HTTP headers and add an Authorization header containing the JWT

With this in place, the JWT initially created on the Authentication server is now sent with every request to the Application server.

Let's now see how the Application server uses the JWT to identify the user.

Validating a JWT on the server side

To authenticate a request, we need to extract the JWT from the Authorization header and check the timestamp and user identifier.

We don't want to apply this logic to all backend routes, since some routes are publicly accessible. For example, login and signup routes should be accessible to any user.

Nor do we want to repeat the authentication logic on a per-route basis, so the best approach is to create an Express Authentication middleware and apply it only to specific routes.

Let's say we've defined an Express middleware called checkIfAuthenticated—a reusable function containing all the authentication logic in one place.

Here's how we can apply it to only certain routes:

In this example, readAllLessons is an Express route serving a JSON list of lessons when a GET request hits the /api/lessons URL.

We've restricted this route to authenticated users by applying the checkIfAuthenticated middleware before the REST endpoint, meaning the order of middleware functions matters.

The checkIfAuthenticated middleware either reports an error if no valid JWT is present, or allows the request to continue through the middleware chain.

The middleware must also throw an error if a JWT is present, correctly signed, but expired. Note that all this logic is the same in any application using JWT-based authentication.

We could write this middleware ourselves using node-jsonwebtoken, but this logic is easy to get wrong, so let's use a third-party library instead.

Configuring a JWT validation middleware using express-jwt

To create the checkIfAuthenticated middleware, we'll use the express-jwt library.

This library lets us quickly create middleware functions for common JWT-based authentication setups, so let's see how we'd use it to validate JWTs like the ones created in the login service (signed with RS256).

Let's start by assuming we've installed the public signature validation key in the server's file system. Here's how we could use it to validate JWTs:

Let's break down this code line by line:

  • we start by reading the public key from the file system, which will be used to validate JWTs
  • this key can only validate existing JWTs, not create or sign new ones
  • we pass the public key to express-jwt, and get back a ready-to-use middleware function!

This middleware throws an error if a correctly signed JWT isn't present in the Authorization header. It also throws an error if the JWT is correctly signed but already expired.

If we'd like to change the default error handling—returning, say, a 401 status code with a JSON payload message instead of throwing—that's also possible.

But one of the main advantages of RS256 signatures is that we don't have to install the public key locally on the application server, as we did in this example.

Imagine the server running multiple instances: replacing the public key everywhere at once would be problematic.

Leveraging RS256 Signatures

Instead of installing the public key on the Application server, it's much better to have the Authentication server publish the JWT-validating public key at a publicly accessible URL.

This provides significant benefits, such as simplified key rotation and revocation. If we need a new key pair, we simply publish a new public key.

Typically, during periodic key rotation, both keys are published and active for a period longer than the session duration to avoid disrupting user experience, while revocation might take effect much faster.

There's no danger that an attacker could exploit the public key. The only thing it allows is validating signatures of existing JWTs, which is useless to an attacker.

There's no way to use the public key to forge new JWTs or infer the private signing key's value.

The question now is: how do we publish the public key?

JWKS (JSON Web Key Set) endpoints and key rotation

JWKS, or JSON Web Key Set, is a JSON-based standard for publishing public keys via a REST endpoint.

The output of such an endpoint might look intimidating, but the good news is we won't have to consume this format directly—a library will handle it transparently:

A couple of details about this format: kid stands for Key Identifier, and the x5c property is the public key itself (it's the x509 certificate chain).

Again, we won't need to write code to consume this format, but it's helpful to understand what this REST endpoint does: it simply publishes a public key.

Implementing JWKS key rotation using the node-jwks-rsa library

Since the public key format is standardized, we need a way to read the key and pass it to express-jwt so it can be used instead of the public key read from the file system.

That's exactly what the node-jwks-rsa library enables! Let's see it in action:

This library reads the public key from the URL specified in the jwksUri property and uses it to validate JWT signatures. All we need to do is configure the URL and, if needed, a few extra parameters.

Configuration options for consuming the JWKS endpoint

The cache parameter set to true is recommended, to avoid retrieving the public key each time. By default, a key is kept for 10 hours before checking if it's still valid, with a maximum of 5 keys cached simultaneously.

The rateLimit property is also enabled, ensuring the library makes no more than 10 requests per minute to the server hosting the public key.

This guards against a denial-of-service scenario where, for any reason (an attack or maybe a bug), the public server is constantly rotating the public key.

That could quickly bring the Application server to a halt, so having built-in defenses is excellent! If you'd like to change these default parameters, check the library docs for more details.

And with that, we've completed the JWT's journey through the network!

  • We created and signed a JWT on the Application server
  • We showed how the client can use the JWT and send it back with each HTTP request
  • we demonstrated how the Application server can validate the JWT and associate each request with a specific user

We've also discussed the many design decisions involved in this roundtrip. Let's summarize what we've learned.

Summary and Conclusions

Delegating security features like Authentication and Authorization to a third-party JWT-based provider or product is more feasible than ever, but that doesn't mean security can be added transparently to an application.

Even with a third-party authentication provider or an enterprise single sign-on solution, we still need to understand how JWTs work at least in some detail—if for no other reason than to comprehend the documentation of the products and libraries we'll choose from.

We'll still need to make many security design decisions ourselves, choose libraries and products, select critical configuration options like JWT signature types, set up hosted login pages if applicable, and implement security-critical code that's easy to get wrong.

I hope this post helps with that and that you enjoyed it! If you have questions or comments, please let me know below and I'll get back to you.

If you'd like to learn much more about securing an Angular application, we recommend the Angular Security Course, where Angular Authentication and Authorization are covered in greater depth.

To get notified when more posts like this come out, I invite you to subscribe to our newsletter:

If you're just getting started with Angular, check out the Angular for Beginners Course:

Angular Authentication With JWT: The Complete Guide — figure 1

The J

AU
Angular University

Writes about RxJS, Components, Signals. Active 2015–2026.

All 79 articles →