/Jwt-Spring-Security-JPA

Backend MVP showcasing JWT (Json Web Token) authentication with multiple login, timeout / refresh / logout (with in memory invalidation) using Spring Security & MySQL JPA.

Primary LanguageJavaApache License 2.0Apache-2.0

Jwt-Spring-Security-JPA

Travis (.org) GitHub

A demo project explaining the backend authentication using JWT (Json Web Token) authentication using Spring Security & MySQL JPA.

There's support for the following features:

  • Conventional email/username based registration with admin support.
  • Conventional Login using Spring Security and generation of JWT token.
  • Multiple device login and logout support.
  • In memory store for blacklisting JWT tokens upon user logout.
  • Expiration bases email verification. Mail is sent upon registration.
  • Resend the email confirmation email if old one expires.
  • Forgot-password functionality with password reset token validations.
  • Admin protected urls leveraging Spring security.
  • Refresh JWT tokens once the temporary JWT expires.
  • Check availability of username/email during registration.


JWT

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.


Swagger Docs

The project has been configured with a Swagger docket that exposes the APIs with the schema

Accessible at http://localhost:9004/swagger-ui.html once the app is running.

image


Exception Handling

  • The app throws custom exceptions wherever necessary which are captured through a controller advice. It then returns the appropriate error response to the caller
  • Moreover, entities are validated using JSR-303 Validation constraints.

Getting Started

Clone the application

$ git clone https://github.com/isopropylcyanide/Jwt-Spring-Security-JPA.git
$ cd Jwt-Spring-Security-JPA

Create a MySQL database

$ create database login_db

Change MySQL username and password as per your MySQL installation

  • Edit spring.datasource.username and spring.datasource.password properties as per your mysql installation in src/main/resources/application.properties
  • Edit spring.mail.username and spring.mail.password properties as per your mail server src/main/resources/mail.properties

Run the app

./mvnw spring-boot:run   # For UNIX/Linux based operating systems
mvnw.cmd spring-boot:run # For Windows based operating systems
  • The server will start on server.port:9004 and will create the tables for you.
  • Every run of the app will reset your state. To not do that, modify spring.jpa.hibernate.ddl-auto: update

API

Registering a User
curl --location --request POST 'localhost:9004/api/auth/register' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "amangarg1995sep@gmail.com",
    "password": "amangarg",
    "registerAsAdmin": true
}'

image

⚠️ If you re-register an email twice, you'll get the "email in use" error


Logging in an unverified user
curl --location --request POST 'localhost:9004/api/auth/login' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "amangarg1995sep@gmail.com",
    "password": "amangarg",
    "deviceInfo": {
        "deviceId": "D1",
        "deviceType": "DEVICE_TYPE_ANDROID",
        "notificationToken": "N1"
    }
}'

image


Confirming the user email verification token
curl --location --request GET 'localhost:9004/api/auth/registrationConfirmation?token=bcbf8764-dbf2-4676-9ebd-2c74436293b9' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "a@b.com",
    "password": "HI12",
    "deviceInfo": {
        "deviceId": "D1",
        "deviceType": "DEVICE_TYPE_ANDROID",
        "notificationToken": "N1"
    }
}'

image

⚠️ If you pass the incorrect token you will get a "Token Mismatch error"

Don't know the token?: Check your email in mail.properties

Still didn't get it?: Look inside the database email_verification_token#token


Logging in the user with valid credentials
curl --location --request POST 'localhost:9004/api/auth/login' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "amangarg1995sep@gmail.com",
    "password": "amangarg",
    "deviceInfo": {
        "deviceId": "D1",
        "deviceType": "DEVICE_TYPE_ANDROID",
        "notificationToken": "N1"
    }
}'

image

⚠️ If you do not enter correct credentials you will get a "Bad credentials error"

⚠️ If your email is not verified (refer the above API) you will get an "Unauthorized" error

❔ Device information is required to enable a multi device login and logout functionality.


Using the JWT token to access a user resource
curl --location --request GET 'localhost:9004/api/user/me' \
--header 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiaWF0IjoxNjM1NjE0NTY4LCJleHAiOjE2MzU2MTU0Njh9.d8CJYduoC44njutphODoezheSt_so3Doc9g1RSiMaDU_qJwY0_3Ym4092hFkHsh-jbyB_9i66LbwSEE-szAgEw'

image

⚠️ If you enter an invalid token (obtained post login), you will get an "Incorrect JWT Signature" error.

⚠️ If you enter a malformed JWT token, you will get a "Malformed JWT Signature" error.

⚠️ If you enter an expired JWT token (default: app.jwt.expiration, you will get an "Expired JWT Signature" error and clients should refresh the JWT token.

image


Using the JWT token to access an admin resource
curl --location --request GET 'localhost:9004/api/user/admins' \
--header 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiaWF0IjoxNjM1NjE0NTY4LCJleHAiOjE2MzU2MTU0Njh9.d8CJYduoC44njutphODoezheSt_so3Doc9g1RSiMaDU_qJwY0_3Ym4092hFkHsh-jbyB_9i66LbwSEE-szAgEw'

image

⚠️ If you registered a user with registerAsAdmin: false, then you will get a "Forbidden" error. image

⚠️ JWT has to be valid (same constraints as the above user resource API)


Logout user
curl --location --request POST 'localhost:9004/api/user/logout' \
--header 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiaWF0IjoxNjM1NjE0NTY4LCJleHAiOjE2MzU2MTU0Njh9.d8CJYduoC44njutphODoezheSt_so3Doc9g1RSiMaDU_qJwY0_3Ym4092hFkHsh-jbyB_9i66LbwSEE-szAgEw' \
--header 'Content-Type: application/json' \
--data-raw '{
    "deviceInfo": {
        "deviceId": "D1",
        "deviceType": "DEVICE_TYPE_ANDROID",
        "notificationToken": "N1"
    }
}'

image

❔ Logging out also deletes the refresh token associated with the device. In real production, this token should be specifically invalidated.

⚠️ If the JWT isn't passed then you will get an "Unauthorized" error.

image

⚠️ If you try to log out same user twice (without an app restart), you will get a "Token Expired" error. This works because on logout we invalidate the JWT

image

⚠️ If you try to log out a logged-in user against an invalid device (say D2), you will get an "Invalid Device" error.

image


Request a reset password link for a registered user
curl --location --request POST 'localhost:9004/api/auth/password/resetlink' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "amangarg1995sep@gmail.com"
}'

image

❔ You can request a password reset multiple times. The reset token would be generated multiple times with an app.token.password.reset.duration

❔ You can request a password reset for a user even when they have not verified their email once. This is okay for our demo case.

⚠️ If you try to request a password reset for an unregistered user, you will get a "No matching user" error


Reset password for a registered user
curl --location --request POST 'localhost:9004/api/auth/password/reset' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "amangarg1995sep@gmail.com",
    "password": "P1",
    "confirmPassword": "P1",
    "token": "880ab6f1-4b4b-4d04-92bd-8995b4063205"
}'

image

⚠️ If your new passwords do not match, there will be an error

⚠️ If your password reset token is not valid or is for some other user, you'll get a "Password Reset Token Not Found" error.

image

⚠️ If you try to use a password reset token twice, you will get a "Token Inactive" error image


Refreshing the JWT token for longer login sessions
curl --location --request POST 'localhost:9004/api/auth/refresh' \
--header 'Content-Type: application/json' \
--data-raw '{
    "refreshToken": "d029e0fa-80f5-4768-837c-7e85a0f94960"
}'

image

❔ You can refresh a JWT multiple times against the refresh token. That is the purpose of refresh. Refresh token expiry can be controlled with app.token.refresh.duration

⚠️ If you pass an invalid refresh token (obtained through login), you will get a "No token found" error

image


Check email in use
curl --location --request GET 'localhost:9004/api/auth/checkEmailInUse?email=amangarg1995sep@gmail.com'

image

❔ The API can be accessed insecurely and hence should be rate limited in production to prevent a DDOS attack.

❔ You can request a password reset for a user even when they have not verified their email once. This is okay for our demo case.

⚠️ If you try to request a password reset for an unregistered user, you will get a "No matching user" error


Roles

  • The spring boot app uses role based authorization powered by spring security
  • Tables and role data should have been created by default upon the first startup.
  • Any new user who signs up to the app is assigned the ROLE_USER by default.
  • In case the role entries aren't created, please execute the following sql queries in the database to insert the USER and ADMIN roles.
INSERT INTO `login_db.role` (ROLE_NAME)
VALUES ('ROLE_USER');
INSERT INTO `login_db.role` (ROLE_NAME)
VALUES ('ROLE_ADMIN');

Contribution

  • Remember, the project is a demo and should not be used into production directly.
  • Please fork the project and adapt it to your use case.
  • Submit a pull request with proper motivation and test plan.
  • Postman collection dump available here)
  • Not everything is in scope for this demo project. Feel free to fork the project and extend the functionality.
  • Project is equipped with a JUnit but lacks tests in most places. Would really appreciate your contributions here.