RESTful Web Service to Authenticate User and Issue Access Token

It time to learn how to create a Web Service to authenticate user with their user name and password and how to issue a unique secure access token which our Mobile Application can use to send HTTP requests and communicate with protected web services of our API. For a free video tutorial on how to build a user interface with Xcode and Swift to implement User Sign up, Sign in, Sign out and learn how to send HTTP Request to a Restful Web Service created in this tutorial check out this Youtube playlist. To how to register a new user or how/save user profile details in database and how to generate a secure user password checkout my previous blog post: RESTful Web Service to Save a New User in Database. Also, the code in this blog post will be based on the code from my previous blog post and this is just to save your time and not to create a new project and then import all the dependencies again and create new service layer, Database Access Object and other classes. So let’s begin.

Access User Name and Password to Authenticate User

Below is the code example of new Root Resource class which I have called Authentication.java and it has its own @Path(“/authentication”). The HTTP POST to this web service endpoint will access the below JSON payload which will then be converted into  LoginCredentials java object:

{
 "userName":"[email protected]",
 "userPassword":"123"
}

The logic in this web service end point is to:

  1. Accept User Login Credentials, extract user name and password and request the Service Layer object which is called authenticationService to authenticate user,
  2. If user authentication is successful, the web service will reset the existing access token(if one exist) and,
  3. Generate a new secure access token which can be send with other HTTP Requests by our mobile application which needs to communicate with protected web service endpoints. Protected web service endpoints are those that require user to be logged in.
package com.appsdeveloperblog.ws.entrypoints;

import com.appsdeveloperblog.ws.service.AuthenticationService;
import com.appsdeveloperblog.ws.shared.dto.UserProfileDto;
import com.appsdeveloperblog.ws.shared.exceptions.AuthenticationException;
import com.appsdeveloperblog.ws.ui.rest.AuthenticationDetails;
import com.appsdeveloperblog.ws.ui.rest.LoginCredentials;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

/**
 *
 * @author skargopolov
 */
@Path("/authentication")
public class Authentication {

    @Autowired
    @Qualifier("authenticationService")
    AuthenticationService authenticationService;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public AuthenticationDetails userLogin(LoginCredentials loginCredentials) {
        AuthenticationDetails returnValue = new AuthenticationDetails();

        UserProfileDto userProfile = null;
        
        // Perform User Authentication
        try {
            userProfile = authenticationService.authenticate(
                    loginCredentials.getUserName(),
                    loginCredentials.getUserPassword());
        } catch (AuthenticationException ex) {
            Logger.getLogger(Authentication.class.getName()).log(Level.SEVERE, null, ex);
            return returnValue;
        }
        
        // Reset Access Token
        authenticationService.resetSecurityCridentials(loginCredentials.getUserPassword(), 
                userProfile);

        // Issue a new Access token
        String secureUserToken = authenticationService.issueSecureToken(userProfile);
        
        returnValue.setToken(secureUserToken);
        returnValue.setId(userProfile.getUserId());

        return returnValue;
    }
}

AuthenticationService class to Perform User Authentication

The logic to authenticate user is delegated to AuthenticationService class and its method authenticate() which takes in as method arguments two values: userName and userPassword. I will paste the entire class with all its methods at the end of blog post but first I will take each of its methods and will give it a little description. Based on provided user name and password, authentication service will generate a secure user password and will then compare provided user name and generated user password with those that we have in database. If values match, authenticate() method will return user profile details. Other wise an exception will be thrown.

@Override
    public UserProfileDto authenticate(String userName, String userPassword) throws AuthenticationException {
        UserProfileDto userProfile = new UserProfileDto();

        UserProfileEntity userEntity = getUserProfile(userName); // User name must be unique in our system

        // Here we perform authentication business logic
        // If authentication fails, we throw new AuthenticationException
        // other wise we return UserProfile Details
        String secureUserPassword = null;

        try {
            secureUserPassword = authenticationUtil.
                    generateSecurePassword(userPassword, userEntity.getSalt());
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(AuthenticationServiceImpl.class.getName()).log(Level.SEVERE, null, ex);
            throw new AuthenticationException(ex.getLocalizedMessage());
        }

        boolean authenticated = false;
        if (secureUserPassword != null && secureUserPassword.equalsIgnoreCase(userEntity.getUserPassword())) {
            if (userName != null && userName.equalsIgnoreCase(userEntity.getUserName())) {
                authenticated = true;
            }
        }

        if (!authenticated) {
            throw new AuthenticationException("Authentication failed");
        }

        BeanUtils.copyProperties(userEntity, userProfile);

        return userProfile;
    }

Issue Secure Access Token

The reset security credentials method is optional and it is up to you if you want to use it but I always do. This is so that every time user logs in a new salt, new secure password and a new access token is generated. This way even if database record gets compromised then the next time user logs in, these values are reset and the previous values can no longer be used.

Also, before you continue further, I would like to mention that I have included everything mentioned in this tutorial, in my video course: REST API with Java JAX-RS. Create and Deploy to Amazon Cloud. So you if you get stack with any of these code examples, you can follow the video tutorial as well. The access token is generated the following way:

  1. Read the value of salt which is current under user profile,
  2. Concatenate the public alphanumeric value of user id and value of salt together to produce access token material. Please note that the public alphanumeric userId value id is different from the sequentially autogenerated database id record,
  3. Use the secure value of user password to encrypt with it the value of access token material to produce the final value of access token. You can also use a SecretKey to encode the value of access token material and keep the SecretKey file outside of web application. This is another very good approach. But then you will also need to implement a SecretKey rotation, so that the SecretKey change from time to time. In this example, we are are using Password Based Encryption and we use secure password as encryption key and the value of secure password and the value of salt changes every time user logs in.
  4. Base64 encode the value of access token,
  5. Split the final value of access token into two parts. They do not need to be equal and intact it is better that they are not equal. Keep one part of secure access token in database and return another part of access token back to mobile application so that it is then gets stored in keychain,
  6. Store one part of access token in database.

The code to issue the secure access token is below:

@Override
public String issueSecureToken(UserProfileDto userProfile) throws AuthenticationException {
    String returnValue = null;

    // Get salt but only part of it
    String newSaltAsPostfix = userProfile.getSalt();
    String accessTokenMaterial = userProfile.getUserId() + newSaltAsPostfix;

    byte[] encryptedAccessToken = null;

    try {
        encryptedAccessToken = authenticationUtil.encrypt(userProfile.getUserPassword(), accessTokenMaterial);
    } catch (InvalidKeySpecException ex) {
        Logger.getLogger(AuthenticationServiceImpl.class.getName()).log(Level.SEVERE, null, ex);
        throw new AuthenticationException("Faled to issue secure access token");
    }

    String encryptedAccessTokenBase64Encoded = Base64.getEncoder().encodeToString(encryptedAccessToken);

    // Split token into equal parts
    int tokenLength = encryptedAccessTokenBase64Encoded.length();
    String tokenToSaveToDatabase = encryptedAccessTokenBase64Encoded.substring(0, tokenLength / 2);
    returnValue = encryptedAccessTokenBase64Encoded.substring(tokenLength / 2, tokenLength);

    userProfile.setToken(tokenToSaveToDatabase);

    storeAccessToken(userProfile);

    return returnValue;
}

Authentication Service. Complete Source Code.

package com.appsdeveloperblog.ws.service.impl;

import com.appsdeveloperblog.ws.io.dao.Database;
import com.appsdeveloperblog.ws.io.entity.UserProfileEntity;
import com.appsdeveloperblog.ws.service.AuthenticationService;
import com.appsdeveloperblog.ws.shared.dto.UserProfileDto;
import com.appsdeveloperblog.ws.shared.exceptions.AuthenticationException;
import com.appsdeveloperblog.ws.shared.exceptions.UserServiceException;
import com.appsdeveloperblog.ws.utils.AuthenticationUtil;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("authenticationService")
public class AuthenticationServiceImpl implements AuthenticationService {
 
    Database database;
    AuthenticationUtil authenticationUtil;
    
    @Autowired
    public AuthenticationServiceImpl(Database database, AuthenticationUtil authenticationUtil)
    {
        this.database = database;
        this.authenticationUtil = authenticationUtil;
    }

    @Override
    public UserProfileDto authenticate(String userName, String userPassword) throws AuthenticationException {
        UserProfileDto userProfile = new UserProfileDto();

        UserProfileEntity userEntity = getUserProfile(userName); // User name must be unique in our system

        // Here we perform authentication business logic
        // If authentication fails, we throw new AuthenticationException
        // other wise we return UserProfile Details
        String secureUserPassword = null;

        try {
            secureUserPassword = authenticationUtil.
                    generateSecurePassword(userPassword, userEntity.getSalt());
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(AuthenticationServiceImpl.class.getName()).log(Level.SEVERE, null, ex);
            throw new AuthenticationException(ex.getLocalizedMessage());
        }

        boolean authenticated = false;
        if (secureUserPassword != null && secureUserPassword.equalsIgnoreCase(userEntity.getUserPassword())) {
            if (userName != null && userName.equalsIgnoreCase(userEntity.getUserName())) {
                authenticated = true;
            }
        }

        if (!authenticated) {
            throw new AuthenticationException("Authentication failed");
        }

        BeanUtils.copyProperties(userEntity, userProfile);

        return userProfile;
    }

    @Override
    public UserProfileDto resetSecurityCridentials(String password, 
            UserProfileDto userProfile) {
        // Generate salt
        String salt = authenticationUtil.generateSalt(30);

        // Generate secure user password 
        String secureUserPassword = null;

        try {
            secureUserPassword = authenticationUtil.
                    generateSecurePassword(password, salt);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(UsersServiceImpl.class.getName()).log(Level.SEVERE, null, ex);
            throw new UserServiceException(ex.getLocalizedMessage());
        }

        userProfile.setSalt(salt);
        userProfile.setUserPassword(secureUserPassword);

        UserProfileEntity userEntity = new UserProfileEntity();
        BeanUtils.copyProperties(userProfile, userEntity);
        // Connect to database 
        try {
            this.database.openConnection();
            this.database.updateUserProfile(userEntity);
        } finally {
            this.database.closeConnection();
        }

        return userProfile;

    }

    @Override
    public String issueSecureToken(UserProfileDto userProfile) throws AuthenticationException {
        String returnValue = null;

        // Get salt but only part of it
        String newSaltAsPostfix = userProfile.getSalt();
        String accessTokenMaterial = userProfile.getUserId() + newSaltAsPostfix;

        byte[] encryptedAccessToken = null;

        try {
            encryptedAccessToken = authenticationUtil.encrypt(userProfile.getUserPassword(), accessTokenMaterial);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(AuthenticationServiceImpl.class.getName()).log(Level.SEVERE, null, ex);
            throw new AuthenticationException("Faled to issue secure access token");
        }

        String encryptedAccessTokenBase64Encoded = Base64.getEncoder().encodeToString(encryptedAccessToken);

        // Split token into equal parts
        int tokenLength = encryptedAccessTokenBase64Encoded.length();
        String tokenToSaveToDatabase = encryptedAccessTokenBase64Encoded.substring(0, tokenLength / 2);
        returnValue = encryptedAccessTokenBase64Encoded.substring(tokenLength / 2, tokenLength);

        userProfile.setToken(tokenToSaveToDatabase);

        storeAccessToken(userProfile);

        return returnValue;
    }

    private void storeAccessToken(UserProfileDto userProfile) {
        UserProfileEntity userEntity = new UserProfileEntity();
        BeanUtils.copyProperties(userProfile, userEntity);

        // Connect to database 
        try {
            this.database.openConnection();
            this.database.updateUserProfile(userEntity);
        } finally {
            this.database.closeConnection();
        }
    }

    private UserProfileEntity getUserProfile(String userName) {
        UserProfileEntity returnValue = null;
        try {
            this.database.openConnection();
            returnValue = this.database.getUserProfile(userName);
        } finally {
            this.database.closeConnection();
        }
        return returnValue;
    }

}

AuthenticationUtil class. Complete Source Code.

package com.appsdeveloperblog.ws.utils;

import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Random;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.springframework.stereotype.Component;

/**
 *
 * @author skargopolov
 */
@Component
public class AuthenticationUtil {

    private static final Random RANDOM = new SecureRandom();
    private static final String ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    private static final int ITERATIONS = 10000;
    private static final int KEY_LENGTH = 256;

    public String generateSalt(int length) {
        StringBuilder returnValue = new StringBuilder(length);

        for (int i = 0; i < length; i++) {
            returnValue.append(ALPHABET.charAt(RANDOM.nextInt(ALPHABET.length())));
        }

        return new String(returnValue);
    }

    public String generateUserId(int length) {
        return generateSalt(length);
    }

    public String generateSecurePassword(String password, String salt) throws InvalidKeySpecException {

        byte[] securePassword = hash(password.toCharArray(), salt.getBytes());

        return Base64.getEncoder().encodeToString(securePassword);

    }

    private byte[] hash(char[] password, byte[] salt) throws InvalidKeySpecException {
        PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
        Arrays.fill(password, Character.MIN_VALUE);
        try {
            SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            return skf.generateSecret(spec).getEncoded();
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new AssertionError("Error while hashing a password: " + e.getMessage(), e);
        } finally {
            spec.clearPassword();
        }
    }

    public SecretKey generateSecretKey() throws NoSuchAlgorithmException {
        SecretKey returnValue = null;
        KeyGenerator secretKeyGenerator = KeyGenerator.getInstance("DESede");
        secretKeyGenerator.init(112);
        returnValue = secretKeyGenerator.generateKey();
        return returnValue;
    }

    public byte[] encrypt(String securePassword, String accessTokenMaterial) throws InvalidKeySpecException {
        return hash(securePassword.toCharArray(), accessTokenMaterial.getBytes());
    }
 
}

AuthenticationDetails class. Source Code. 

package com.appsdeveloperblog.ws.ui.rest;

import javax.xml.bind.annotation.XmlRootElement;

/**
 *
 * @author skargopolov
 */
@XmlRootElement
public class AuthenticationDetails {
    private String id;
    private String token;

    /**
     * @return the id
     */
    public String getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * @return the token
     */
    public String getToken() {
        return token;
    }

    /**
     * @param token the token to set
     */
    public void setToken(String token) {
        this.token = token;
    }
}

Database Access Object. Complete Source Code.

package com.appsdeveloperblog.ws.io.dao.impl;

import com.appsdeveloperblog.ws.utils.HibernateUtils;
import com.appsdeveloperblog.ws.io.dao.Database;
import com.appsdeveloperblog.ws.io.entity.UserProfileEntity;
import javax.persistence.Query;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;

/**
 *
 * @author skargopolov
 */
@Repository
public class MySQLDAO implements Database {

    Session session;

    @Override
    public void openConnection() {
        SessionFactory sessionFactory = HibernateUtils.getSessionFactory();
        this.session = sessionFactory.openSession();
    }

    @Override
    public UserProfileEntity saveUserProfile(UserProfileEntity userProfile) {

        this.session.beginTransaction();
        this.session.clear();
        this.session.save(userProfile);
        this.session.getTransaction().commit();

        return userProfile;
    }

    @Override
    public UserProfileEntity updateUserProfile(UserProfileEntity userProfile) {

        this.session.beginTransaction();
        this.session.update(userProfile);
        this.session.getTransaction().commit();

        return userProfile;
    }

    @Override
    public void closeConnection() {
        this.session.close();
    }

    @Override
    public UserProfileEntity getUserProfile(String userName) {
        UserProfileEntity returnValue = null;

        this.session.beginTransaction();

        Query query = session.createQuery("from Profile where userName='" + userName + "'");
        returnValue = (UserProfileEntity) query.getSingleResult();

        this.session.getTransaction().commit();

        return returnValue;
    }
}

User Profile Entity. Source Code.

package com.appsdeveloperblog.ws.io.entity;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

/**
 *
 * @author skargopolov
 */
@Entity(name = "Profile")
public class UserProfileEntity implements Serializable {

    private static final long serialVersionUID = 7290798953394355234L;

    @Id
    @GeneratedValue
    private long id;
    private String firstName;
    private String lastName;
    private String fullName;
    private String userId;
    private String salt;
    private String token;
    private String userPassword;
    private String userName;

    /**
     * @return the firstName
     */
    public String getFirstName() {
        return firstName;
    }

    /**
     * @param firstName the firstName to set
     */
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    /**
     * @return the lastName
     */
    public String getLastName() {
        return lastName;
    }

    /**
     * @param lastName the lastName to set
     */
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    /**
     * @return the id
     */
    public long getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(long id) {
        this.id = id;
    }

    /**
     * @return the fullName
     */
    public String getFullName() {
        if (fullName == null) {
            fullName = getFirstName() + " " + getLastName();
        }

        return fullName;
    }

    /**
     * @param fullName the fullName to set
     */
    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    /**
     * @return the token
     */
    public String getToken() {
        return token;
    }

    /**
     * @param token the token to set
     */
    public void setToken(String token) {
        this.token = token;
    }

    /**
     * @return the userId
     */
    public String getUserId() {
        return userId;
    }

    /**
     * @param userId the userId to set
     */
    public void setUserId(String userId) {
        this.userId = userId;
    }

    /**
     * @return the salt
     */
    public String getSalt() {
        return salt;
    }

    /**
     * @param salt the salt to set
     */
    public void setSalt(String salt) {
        this.salt = salt;
    }

    /**
     * @return the userPassword
     */
    public String getUserPassword() {
        return userPassword;
    }

    /**
     * @param userPassword the userPassword to set
     */
    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }

    /**
     * @return the userName
     */
    public String getUserName() {
        return userName;
    }

    /**
     * @param userName the userName to set
     */
    public void setUserName(String userName) {
        this.userName = userName;
    }
}

Do not forget that I have included everything mentioned in this tutorial, in my video course: REST API with Java JAX-RS. Create and Deploy to Amazon Cloud. So if for some reason, your code does not work as expected, you can follow my video lessons.

I think with the above source code available, you should be able to implement user authentication for your restful web service apis. Please let me know if you have questions my posting your comments below. Also if you are interested to learn more about RESTful Web Services checkout the page I have created with the Resources for Full Stack Mobile App Developers.

Happy Coding!

Java Web Services Part 1. Video Course.

Java icon

Java Web Services Part 2. Video Course.

Master advanced web services concepts and implement them in easy steps Java Web Services Part 2 icon

REST Java Web Services. Video Course.

A guide to understanding, accessing, and writing a REST Java web service using Apache and Java EE. Java Web Services Part 2 icon

2 Comments on "RESTful Web Service to Authenticate User and Issue Access Token"

Leave a Reply

Your email address will not be published. Required fields are marked *