[Angular + Symfony] JWT Authentication

After a quick setup of the Angular + Symfony Application, the first feature I want to create is the login system. This system has 2 tasks: create an account and login with an account. This article will focus on the second task.

JWT Authentication is the mechanism I used. This article will, first, introduce JWT Authentication, then, present how to apply it in Angular + Symfony Application.

JWT Authentication Introduction

Before talking about JWT Authentication, let’s talk about the most popular authentication mechanism: session-based authentication. It brings us the whole picture of authentication.

After the user sends his username and password to the server, the server checks them by looking up database. If the login is successful, a session ID will be created for the user. This session ID is stocked on the server, and sent back to the user. Next time, the user uses the application, he doesn’t need to send his username and password again, instead, he sends his session ID, and the server checks it to decide if the user can continue using the application.

If user logs off, the server will delete his session ID.

session-based authentication

Session-based authentication is simple and efficient to use, but it has a big disadvantage: The session ID is bound with the server. If our application has multiple servers controlled by on server balancer. The first time, the user logs in. The balancer sends the user to server one, server one creates a session ID for him. Next time, the user uses the application, the balancer sends him to server two, server two can’t identify him :(

Session-based authentication can’t work with multiple servers

In modern application, it’s common to use several servers together (like Micro Service Architecture). So it’s important to solve this problem.

Can we decouple session ID and server ? Yes. Json Web Tokens (JWT) Authentication is created.

Like, session-based authentication, after user logs in successfully in the first time, the server will send back something, which is JWT, to the user. Unlike, session-based authentication, the server keeps nothing. Next time, user uses the application, he sends his request with his JWT, the server checks the JWT, and gives the response.

JWT Authentication

You probably notice that one of the main difference between JWT and session-based Authentication is the place the user information is saved. Session-base Authentication saves it on the server (session ID is bound with the server), while JWT Authentication saves it in JWT.

I will, first, present what is JWT, then explain how JWT Authentication works.

JWT (presented in the left block of the image below) doesn’t look like Json because it is encoded by Base64. It’s not for the security reason, anyone can decode it using Base64. It just transforms json to string. JWT is composed by 3 parts, header, payload and signature, split by dots.

jwt example from jwt.io debugger

The header indicates the algorithm used to generate the signature. In the example above, it uses H5256.

The payload contains user information. We can add any information we want. Usually, there are username and expiration time. Remember, JWT can be decode by anyone, DO NOT add confidential information.

The signature key part of JWT Authentication. It is generated by the first 2 parts, and the secret key created by application.

So how JWT Authentication works ? After user logs in successfully at the first time, the server will keep his public information, such as username, in JWT, and using its secret key to add a signature. When user sends a request with JWT, the server will, first, decode the first 2 parts of JWT for understanding who asks for response. Then, the server using these information and its secret key to generate a new signature, and it compare the third part of JWT with the new signature to tell if this request is authenticated.

Now, we can see how important the secret key is. NEVER expose the secret key to the public !

JWT Authentication solves the problem of multiple servers. All the servers share the same secret key.

There are some disadvantages of JWT Authentication. Unlike session-based authentication, JWT can not be disable manually. The only way to disable it is setting expiration date. Even user logs out, his JWT is still enabled if it is not expired. To keep user safe, we can set the expiration date short, like one day. But, if JWT is expired, user need to log in again, which can be annoying for user who uses the application frequently. He needs to log in everyday.

JWT Authentication is not perfect. Since it solves multiple servers problem, and it’s simple to apply, it is still worthy of using it in the application.

There is a great video of Java Brains which explains JWT, and I learnt JWT from it. I link it here.

JWT Authentication Implementation

Enough of theory, let’s code :)

First, I will show you how to generate and verify JWT at server-side using Symfony, then I will present how to receive JWT and send request with JWT at client-side using Angular.

Server-side: Symfony

Symfony has a package for JWT Authentication: LexikJWTAuthenticationBundle. It makes the implementation so easy. There are 3 steps: Install the package, generate RSA public key and private key using the secret key, configure security and route file.

Install the package

php composer.phar require "lexik/jwt-authentication-bundle"

After installing it, you will see that the secret key is created by the package in .env file.

###> lexik/jwt-authentication-bundle ###JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pemJWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pemJWT_PASSPHRASE=your_secret_key###< lexik/jwt-authentication-bundle ###

Again, NEVER push these information to your Github public repository ! Keep it to the .env.local.

Generate RSA keys as secret key

mkdir -p config/jwtopenssl genpkey -out config/jwt/private.pem -aes256 -algorithm rsa -pkeyopt rsa_keygen_bits:4096openssl pkey -in config/jwt/private.pem -out config/jwt/public.pem -pubout

You need to type your_secret_key while executing these codes.

Configure security and route file

In config/packages/security.yaml

# ...
class: App\Entity\User
property: email
pattern: ^/api/login
stateless: true
anonymous: true
provider: app_user_provider
check_path: /api/login_check
username_path: email
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure

pattern: ^/api
stateless: true
- lexik_jwt_authentication.jwt_token_authenticator

- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }

In route file

path: /api/login_check

In this example, we use App\Entity\User as login provider, which means the server checks the User Table in database to verify if the user exists. Other types of providers, such as LDAP, can also use JWT Authentication by adding success_handler and failure_handle as presented in the example.

Now, we can send username and password to /api/login_check to login. If log in, we can receive JWT.

curl -X POST -H "Content-Type: application/json" http://localhost/api/login_check -d '{"username":"johndoe","password":"test"}'

Client-side: Angular

At client-side, we need to do 3 things: send username + password for the first time login, receive JWT and store it, add JWT to every request.

I create AuthService to do the first 2 things in login function. I post username and password to /api/login_check endpoint. If login is successful, I store JWT in localStorage. You can store it in cookie, too. It depends on what you like.

Finally, to add jwt to request, I create a http Interceptor. Before sending each request, JwtInterceptor will read JWT in localStorage, if JWT exists, it will add JWT to request header. Since the data in header is in the format of key-value, JWT is sent by Authorization: Bearer [your-jwt].

To enable JwtInterceptor, we need to inject it into our application. Go to the app.module.ts, add it into providers of NgModule.

providers: [
{provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true},

That’s all for JWT Authentication. Thanks for reading :)

Developper in Paris, interested in Big Front-end, Full Stack, Explainable AI. Try to introduce AI into Full Stack workflow