Session 10.1: "Secure" project (A Single Spring Boot App + VueJs) - Auth Service (JWT)
Note this content is covered by Session 10.2, it is a more simplified version for a single spring boot application.
1. Clone the following repository
$ git clone https://github.com/M-Gharib/ESI-W10.1.git
2. Project setup - install dependencies
$ npm install
3. Create the JAR for the backend (server)
$ npm run server-authservice-package
4. Run the backend (server)
$ npm run server-authservice
Note: If you have any problems while packaging and running the backend via the command line, you can simply run it manually as we did before.
5. Run the frontend (VueJs project) - in a new terminal
$ npm run serve
Note: If you want to create the authentication service (Spring Boot project) from scratch, please refer to the previous week's materials. If you want to create the front-end project (VueJs) from scratch, please refer to the material of week 8 (Vue.js). You just need to install jwt-decode
, after creating the VueJs project, as follows
$ git npm install jwt-decode
Backend (Spring Boot)
Our backend Authentication service provides six different request handlers (functions) to deal with the six different HTTP requests we have:
Method | URI | Action |
POST | http://localhost:8090/api/auth/signup | Create/register a new user |
POST | http://localhost:8090/api/auth/login | Login a registered user |
GET | http://localhost:8090/api/auth/authenticate | Authenticate a user |
GET | http://localhost:8090/api/auth/public | Public endpoint |
GET | http://localhost:8090/api/auth/admin | Protected endpoint - only users (Role USER) are allowed |
GET | http://localhost:8090/api/auth/user | Protected endpoint - only users (Role ADMIN) are allowed |
The code is fully annotated to facilitate your comprehension. With respect to the previous week's project, there are a few changes:
- In
UserController.java
, the@GetMapping("/authenticate")
receives the jwt token from the client, tries to validate it (depending on thevalidateToken(String token)
function), and returns the result. - In
JwtService.java
, thevalidateToken(String token)
function takes the token as an input, tries to validate it, and returns a boolean response concerning the result of validation.
Frontend (VueJs)
The frontend application, when started, tries to visit the Home page, which is protected. Accordingly, the user is redirected to the login page. If the user is registered/signed up, she/he can enter her/his credentials. If the credentials are correct, she/he will receive a jwt token that grants her/him access to the Home page.
If the user is not registered, she/he can register via the signup page. Similarly, she/he will receive a jwt token that grants her/him access to the Home page after signing up. In both cases, the jwt token is saved in the local storage to be used when needed.
The Home page checks the role of the logged-in/signed-up user, then, fetches and presents the content related to the specified role. In short, the jwt token is fetched and added to the header of the request for getting the corresponding content.
After successful login/signup, the Home page will show a button that allows the user to log out. In particular, when the log-out button is clicked on, the jwt that is saved in the local storage is deleted and the user is redirected to the Home page.
A simplified representation of the "workflow" of the application, and the sign-in, login, authenticate, and logout workflow are shown in the following two figures.
The frontend application consists of several views and an auth.js file. We briefly describe each of them here:
SignUp.vue
contains a form that enables a user to enter his credentials (username, password, and roles). Note that the role is not a usual part of such form, which is usually assigned either a default role (e.g., a user, a customer) or set by the system administrator. We are allowing setting the role in the signup process for demonstrating how the auth/auth works. The entered credentials are sent to the server when the user press on the signup button. The server sends a jwt token as a response. The returned token is decoded, the role is abstracted from it, and saved in the local storage. Then, the user will be redirected to the Homepage, which will check whether the user can be authenticated, and since she/he has a valid token, she/he will be granted access.LogIn.vue
contains a form that enables a user to enter his credentials (username and password). The entered credentials are sent to the server when the user press on the login button. The server checks the credentials and if they are valid, it sends a jwt token as a response. The returned token is decoded, the role is abstracted from it, and saved in the local storage. Then, the user will be redirected to the Homepage, which will check whether the user can be authenticated, and since she/he has a valid token, she/he will be granted access.HomeView.vue
contains two simple templates dedicated to the two different roles a user can have ('ADMIN' and 'USER'). After a user is authenticated, its role will be specified, and based on such role one of these templates will be shown. On mount, the content of the template will be fetched from a protected end-point related to the logged-in role.HomeView.vue
also contains a logout button that will be shown to users that are authenticated, which enables them to log out.AboutView.vue
the default about view page.auth.js
includes the following essential functions:authenticated
checks if there is a jwt token saved in the local storage, then, sends it to the server to be validated. If the token is valid, the server returns a positive response and the user is authenticated. Otherwise, the user cannot be authenticated.hasARoleOf
checks and returns the role of the logged/signed-in user, the role is abstracted from the returned or stored jwt token.logout
log the user out by removing/deleting the jwt that is saved in the local storage.
How we are controlling the user access in the Frontend (VueJs)?
The following snippet in router/index.js
checks whether the user that is trying to visit the home page is authenticated. Accordingly, he/she might be granted access or redirected to the login page.
//router/index.js ... import auth from "../auth"; ... const routes = [{ path: "/", name: "home", component: HomeView, beforeEnter: async(to, from, next) => { let authResult = await auth.authenticated(); if (!authResult) { next('/login') } else { next(); } } }, ...