Skip to main content

Lack of multi-factor authentication

Need

Implementation of multi-factor authentication for critical services

Context

  • Usage of Scala 2.12 for building scalable and high-performance applications
  • Usage of play.api.mvc for building web applications with the Play Framework
  • Usage of play.api.data for handling form data in Play Framework
  • Usage of play.api.data.Forms for handling form data in Play Framework

Description

Non compliant code

import play.api.mvc._
import play.api.data._
import play.api.data.Forms._

case class User(username: String, password: String)

val userForm = Form(
mapping(
"username" -> text,
"password" -> text
)(User.apply)(User.unapply)
)

def login = Action { implicit request =>
val user = userForm.bindFromRequest.get
// validate user credentials
if (validateUserCredentials(user)) {
// login successful, start a new session
Redirect(routes.Application.index).withSession("username" -> user.username)
} else {
// login failed
Redirect(routes.Application.login)
}
}

def validateUserCredentials(user: User): Boolean = {
// check if the username and password match stored records
// this is a placeholder, in a real application you would fetch user records from a database
user.username == "admin" && user.password == "password"
}

The above code represents a very basic form of authentication - username and password based authentication. When a user submits the login form, the login function is called. This function extracts username and password from the form data, creates a User object, and calls validateUserCredentials to check if the provided credentials are valid.

In the validateUserCredentials function, it is simply checking if the username is "admin" and the password is "password". If the credentials are valid, a new session is started and the user is redirected to the index page. If the credentials are not valid, the user is redirected back to the login page.

This code is vulnerable because it only relies on a single factor for authentication - something the user knows (their username and password). If an attacker is able to guess or otherwise obtain a user's credentials, they can gain access to the system. A more secure system would also include a second factor of authentication - something the user has (like a phone or hardware token) or something the user is (like a fingerprint or other biometric data).

Steps

  • Implement a multi-factor authentication mechanism for critical services.
  • Use strong authentication factors such as something the user knows (password), something the user has (smart card, token), and something the user is (biometric data).
  • Integrate the multi-factor authentication mechanism with the Play framework's authentication and authorization system.
  • Ensure that the multi-factor authentication is enforced for accessing critical services, such as databases, shared resources, and web services.
  • Store and manage user authentication factors securely, such as using encryption and secure storage mechanisms.
  • Regularly review and update the multi-factor authentication mechanism to address any emerging vulnerabilities or weaknesses.

Compliant code

import play.api.mvc._
import play.api.data._
import play.api.data.Forms._

case class User(username: String, password: String, token: String)

val userForm = Form(
mapping(
"username" -> text,
"password" -> text,
"token" -> text
)(User.apply)(User.unapply)
)

def login = Action { implicit request =>
val user = userForm.bindFromRequest.get
// validate user credentials
if (validateUserCredentials(user) && validateUserToken(user)) {
// login successful, start a new session
Redirect(routes.Application.index).withSession("username" -> user.username)
} else {
// login failed
Redirect(routes.Application.login)
}
}

def validateUserCredentials(user: User): Boolean = {
// check if the username and password match stored records
// this is a placeholder, in a real application you would fetch user records from a database
user.username == "admin" && user.password == "password"
}

def validateUserToken(user: User): Boolean = {
// check if the token is valid
// this is a placeholder, in a real application you would validate the token with a second factor authentication service
user.token == "valid_token"
}

The code above has been modified to include multi-factor authentication. The User case class now includes a token field, which represents the second factor of authentication. This could be a code sent to the user's email or phone, a biometric factor, or a physical device the user possesses.

The userForm mapping has been updated to include this token field. This means that when a user submits the login form, they must now also include their second factor token.

The login action now checks not only the user's credentials (username and password), but also the validity of their token. This is done by the validateUserToken function, which in this example simply checks if the token equals "valid_token". In a real application, this function would communicate with the second factor authentication service to verify the token.

If both the user's credentials and token are valid, the user is logged in and a new session is started. If either are invalid, the login fails and the user is redirected to the login page.

This code ensures that even if an attacker compromises a user's username and password, they cannot access the system without also having access to the user's second factor token. This greatly increases the security of the system.

References