Skip to main content

Kotlin + Spring + Vue: Authentication

Now we have enough knowledge
to implement critical important feature of any enterprise(and not only) application - authentication. We will use JWT authentication method.

JSON Web Token (JWT) is an Internet standard for creating JSON-based access tokens that assert some number of claims. For example, a server could generate a token that has the claim "logged in as admin" and provide that to a client. The client could then use that token to prove that it is logged in as admin. The tokens are signed by one party's private key (usually the server's), so that both parties (the other already being, by some suitable and trustworthy means, in possession of the corresponding public key) are able to verify that the token is legitimate. The tokens are designed to be compact, URL-safe, and usable especially in a web-browser single-sign-on (SSO) context. JWT claims can be typically used to pass identity of authenticated users between an identity provider and a service provider, or any other type of claims as required by business processes.

JWT relies on other JSON-based standards: JSON Web Signature and JSON Web Encryption.


Wikipedia

Objectives

  • Ability to register
  • Ability to authorize
  • Role-based access to data
  • Role-based acmes to pages
  • Recognize user by request
  • Different navbars for authorized and non-authorized users
  • Different navbars for admins and regular users

Why JWT?

  • Why JWT not Basic Authentication? 
    • Basic authentication does not meet the current threat level and is too vulnerable
    • You can find much more info about Basic Authentication implementation in the Internet
  • Why JWT not OAuth?
    • Sometimes you may need to implement custom authentication logic, and it's more convenient to do that using JWT
    • You can find much more info about OAuth Authentication implementation in the Internet
    • If you understand how to implement JWT authentication, implementing OAuth authentication will not cause you any difficulties

Backend

Database

Create table users:

CREATE TABLE public.users
(
     id serial NOT NULL,
     username character varying,
     first_name character varying,
     last_name character varying,
     email character varying,
     password character varying,
     enabled boolean,
     PRIMARY KEY (id)
);



Create table roles:

CREATE TABLE public.roles
(
     id serial NOT NULL,
     name character varying,
     PRIMARY KEY (id)
);



Create table users_roles:

CREATE TABLE public.users_roles
(
     id serial NOT NULL,
     user_id integer,
     role_id integer,
     PRIMARY KEY (id)
);



Add constraints:

ALTER TABLE public.users_roles
     ADD CONSTRAINT users_roles_users_fk FOREIGN KEY (user_id)
     REFERENCES public.users (id) MATCH SIMPLE
     ON UPDATE CASCADE
     ON DELETE CASCADE;


ALTER TABLE public.users_roles
     ADD CONSTRAINT users_roles_roles_fk FOREIGN KEY (role_id)
     REFERENCES public.roles (id) MATCH SIMPLE
     ON UPDATE CASCADE
     ON DELETE CASCADE;



Add roles entries into roles:

INSERT INTO roles (name) VALUES ('ROLE_USER'), ('ROLE_ADMIN');


We don't add users entries right now. We will register them using our application.

JPA

Entities

Create two classes inside the jpa package:
User:
Role:

Repositories

Create two classes inside the repository package:
UsersRepository:
RolesRepository:

pom.xml

Add the following dependencies to your backend module:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
     <groupId>com.fasterxml.jackson.module</groupId>
     <artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt</artifactId>
     <version>0.9.0</version>
</dependency>
<dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt-api</artifactId>
     <version>0.10.6</version>
</dependency>



Application Properties


assm.app.jwtSecret=jwtAssmSecretKey
assm.app.jwtExpiration=86400


Models

We need to create special classes to process data coming from the frontend: for authorizing the existing users and for registering the new users. Let's create package model and these models:
LoginUser:
NewUser:

Responses

Create a package web.response and two small classes in there:
JwtResponse:

import org.springframework.security.core.GrantedAuthority

class JwtResponse(var accessToken: String?, var username: String?, val authorities:

     Collection<GrantedAuthority>) {
     var type = "Bearer"
}



ResponseMessage:

class ResponseMessage(var message: String?)


We also need an error response "User already exists", so create it in web.error package:
UserAlreadyExistException:

class UserAlreadyExistException : RuntimeException {

     constructor() : super() {}

     constructor(message: String, cause: Throwable) : super(message, cause) {}

     constructor(message: String) : super(message) {}

     constructor(cause: Throwable) : super(cause) {}

     companion object {

          private val serialVersionUID = 5861310537366287163L

     }
}



User Service

We need information about user roles to differentiate access to data and services. So, let's create package service and UserDetailsServiceImpl - the implementation of UserDetailsService Spring interface:


JWT

Create package jwt with three following classes:
  • JwtAuthEntryPoint - to handle authorization errors and use it in web security config
  • JwtProvider - to generate and validate tokens and also to get user's data by token provided
  • JwtAuthTokenFilter - to authenticate users and filter the requests

JwtAuthEntryPoint:
JwtProvider:
JwtAuthTokenFilter:

Security Configuration

Create package config and add WebSecurityConfig bean:
We have allowed access to all routes (#56) - the access control will be implemented specifically for every route.

Controllers

Let's create new controller called AuthController:
We have definŠµd two methods, accessible in /api/auth root route:

  • signin - we check if user exists and if so, we return the generated token, username and his authorities (roles)
  • signup - we check if user doesn't exist and if so, we create new record in users table with ROLE_USER role

Let's add some modifications to our BackendController - create two methods:
  1. Return data available for regular users (ROLE_USERS) and admins (ROLE_ADMIN)
  2. Return data available for admins only (ROLE_ADMIN)
We will implement this feature by using @PreAuthorize annotation

import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.Authentication
import com.kotlinspringvue.backend.repository.UserRepository
import com.kotlinspringvue.backend.jpa.User



@Autowired
lateinit var userRepository: UserRepository



@GetMapping("/usercontent")
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
@ResponseBody
     fun getUserContent(authentication: Authentication): String {
     val user: User = userRepository.findByUsername(authentication.name).get()
     return "Hello " + user.firstName + " " + user.lastName + "!"
}


@GetMapping("/admincontent")
@PreAuthorize("hasRole('ADMIN')")
@ResponseBody
     fun getAdminContent(): String {
     return "Admin's content"
}


Build project to ensure everything is OK. That's all for backend.

Frontend

Create new components:
  • Home
  • SignIn
  • SignUp
  • UserPage
  • AdminPage
With the following code draft:

<template>
     <div>
     </div>
</template>

<script>
</script>

<style>
</style>



Add id="component_name" to every <div> inside the <template> and export default {name: ‘[component_name]’} to <script>.

Having so many pages now we need to use router for describing and managing routes: add file router.js to /src:

Vuex

Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. It also integrates with Vue's official devtools extension to provide advanced features such as zero-config time-travel debugging and state snapshot export / import.

We will use Vuex for storing and using authentication token in our requests.

Execute command to install Vuex and add it to your project:

$ npm install --save vuex


Store

Add package src/store and file index.js:
This small module allows us to manage the state using the store:
  • store - the goal data we need to use across the components
  • getters - functions to define specific aspects of state
  • mutations - functions to mutate the state
  • actions - functions to commit the mutations, they can contain asynchronous operations

NOTE: Mutations is the only correct way to change the state


main.js

Add some changes to main.js:

import { store } from './store';

...

new Vue({
     router,
     store,
     render: h => h(App)
}).$mount('#app')


Bootstrap

Install Bootstrap:

$ npm install --save bootstrap bootstrap-vue


Import Bootstrap in main.js:

import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'



Vue.use(BootstrapVue)



App.vue

  • Logout function should be available from everywhere
  • After logout user should be redirected to the start page
  • We should show "Logout" button and "User" tab if user is authenticated and "Login" button if is not
  • We should show "Admin" tab for authenticated admins only
Add method:

methods: {
     logout() {
          this.$store.dispatch('logout');
          this.$router.push('/')
     }
}



Update the template - add the navigation bar:

<template>
     <div id="app">
          <b-navbar style="width: 100%" type="dark" variant="dark">
               <b-navbar-brand id="nav-brand" href="#">Kotlin+Spring+Vue</b-navbar-brand>
               <router-link to="/"><img height="30px" src="./assets/img/kotlin-logo.png" alt="Kotlin+Spring+Vue"/></router-link>
               <router-link to="/"><img height="30px" src="./assets/img/spring-boot-logo.png" alt="Kotlin+Spring+Vue"/></router-link>
               <router-link to="/"><img height="30px" src="./assets/img/vuejs-logo.png" alt="Kotlin+Spring+Vue"/></router-link>
               <router-link to="/user" class="nav-link text-light" v-if="this.$store.getters.isAuthenticated">User</router-link>
               <router-link to="/admin" class="nav-link text-light" v-if="this.$store.getters.isAuthenticated && this.$store.getters.isAdmin">Admin</router-link>
               <router-link to="/register" class="nav-link text-light" v-if="!this.$store.getters.isAuthenticated">Register</router-link>
               <router-link to="/login" class="nav-link text-light" v-if="!this.$store.getters.isAuthenticated">Login</router-link>
               <a href="#" class="nav-link text-light" v-if="this.$store.getters.isAuthenticated" v-on:click="logout">Logout </a>
          </b-navbar>
          <router-view></router-view>
     </div>
</template>


Home.vue

Here we should show "Login" button if user is not authenticated


SignIn.vue

  • Send login request to server
  • Get token from server and save it to storage
  • Show "beautiful" error using Bootstrap (<b-alert>)
  • If logging in is successful, redirect to /home

SignUp.vue

  • Send SignUp request to server
  • Show "beautiful" error using Bootstrap (<b-alert>)
  • Validate input fields

UserPage.vue

Here we get user/admin content from server 

AdminPage.vue

Here we get admin only content

Login and Start

Register user admin user using GUI we created before:



ATTENTION! Add ROLE_ADMIN role to admin user before login:
INSERT INTO users_roles (user_id, role_id) VALUES (1, 2);

Check:

SELECT *
FROM users
INNER JOIN users_roles ON users.id = users_roles.user_id
INNER JOIN roles ON users_roles.role_id = roles.id;

Then:
#1 Login using admin credentials
#2 Check out User page

#3 Check out Admin page

#4 Logout
#5 Register regular user account
#6 Check out User page
#7 Try to get admin content by regular user using REST API: http://localhost:8080/api/admincontent

ERROR 77100 --- [nio-8080-exec-2] c.k.backend.jwt.JwtAuthEntryPoint : Unauthorized error. Message - Full authentication is required to access this resource


Ways to Improve

  • Don't user local storage! That's not secure
  • Use OAuth
  • Use email verification
  • Use reCAPTCHA

Links

Comments

Popular posts from this blog

Make Authentication More Secure With Cookies Email Registration Confirmation That's all for this year. See you in 2020!

Kotlin + Spring Boot + Vue.js: + Gradle

Hello, dear visitors! I added two new articles to the Fullstack section: Kotlin + Spring + Vue: Migration from Maven to Gradle Kotlin + Spring + Vue: Deploy to Heroku with Gradle I'll be glad if you will find then useful for yourself.

Kotlin + Spring Boot + Vue.js

Hello there! I've published my first set of articles about developing web applications using Kotlin , Spring Boot and Vue.js : Koltin + Spring Boot + Vue.js Here are the contents of this set: Introduction Start CI/CD REST API Database Connection Authentication Spam Protection (reCAPTCHA) Email