From 81d0d66d14476ea9ecf40bc3ff9ff50fc9b8b35e Mon Sep 17 00:00:00 2001 From: Felipe D'Abrantes <felidabrantes@gmail> Date: Tue, 11 Apr 2023 00:48:26 +0100 Subject: [PATCH] Create authentication middleware --- .../actions/AuthenticatedUserAction.scala | 29 ++++++++ .../models/actions/AuthenticationFilter.scala | 29 ++++++++ .../actions/AuthenticationRequest.scala | 66 +++++++++++++++++++ backend-services/feed-service/build.sbt | 1 + .../feed-service/conf/application.conf | 3 + 5 files changed, 128 insertions(+) create mode 100644 backend-services/feed-service/app/models/actions/AuthenticatedUserAction.scala create mode 100644 backend-services/feed-service/app/models/actions/AuthenticationFilter.scala create mode 100644 backend-services/feed-service/app/models/actions/AuthenticationRequest.scala diff --git a/backend-services/feed-service/app/models/actions/AuthenticatedUserAction.scala b/backend-services/feed-service/app/models/actions/AuthenticatedUserAction.scala new file mode 100644 index 00000000..7382920c --- /dev/null +++ b/backend-services/feed-service/app/models/actions/AuthenticatedUserAction.scala @@ -0,0 +1,29 @@ +package models.actions + +import models.actions.AuthenticationRequest +import play.api.mvc.{ActionBuilder, BodyParsers, Request, Result, AnyContent} + +import scala.concurrent.Future + +import javax.inject.Inject +import scala.concurrent.ExecutionContext + + +/** + * The authentication action builder that combines the request transformation and filtering. + */ +class AuthenticatedUserAction @Inject()(authenticationTransformer: AuthenticationTransformer, authenticationFilter: AuthenticationFilter) + (implicit val parser: BodyParsers.Default, val executionContext: ExecutionContext) + extends ActionBuilder[AuthenticationRequest, AnyContent] { + + /** + * Invoke the main controller block, with the transformations and filtering middleware. + * + * @param request The incoming request. + * @param block The block of code to invoke. + * @return A future of the result. + */ + override def invokeBlock[A](request: Request[A], block: AuthenticationRequest[A] => Future[Result]): Future[Result] = { + (authenticationTransformer andThen authenticationFilter).invokeBlock(request, block) + } +} diff --git a/backend-services/feed-service/app/models/actions/AuthenticationFilter.scala b/backend-services/feed-service/app/models/actions/AuthenticationFilter.scala new file mode 100644 index 00000000..3e8ed31a --- /dev/null +++ b/backend-services/feed-service/app/models/actions/AuthenticationFilter.scala @@ -0,0 +1,29 @@ +package models.actions + +import models.actions.AuthenticationRequest +import play.api.mvc.{ActionFilter, Result, Results} + +import scala.concurrent.{Future, ExecutionContext} + +import javax.inject.Inject + + +/** + * The authentication action filter that verifies the request contains a user ID. + */ +class AuthenticationFilter @Inject() (implicit val executionContext: ExecutionContext) extends ActionFilter[AuthenticationRequest] { + + /** + * Determines whether to process a request. + * Decides whether to immediately intercept the request or continue processing the request. + * + * @param request The incoming request. + * @return An optional Forbidden Result with which to abort the request. + */ + override def filter[A](request: AuthenticationRequest[A]): Future[Option[Result]] = Future.successful { + if (!request.userId.isDefined) + Some(Results.Forbidden("Invalid JWT Token")) + else + None + } +} diff --git a/backend-services/feed-service/app/models/actions/AuthenticationRequest.scala b/backend-services/feed-service/app/models/actions/AuthenticationRequest.scala new file mode 100644 index 00000000..cf6333b7 --- /dev/null +++ b/backend-services/feed-service/app/models/actions/AuthenticationRequest.scala @@ -0,0 +1,66 @@ +package models.actions + +import com.typesafe.config.ConfigFactory + +import play.api.mvc.{ActionTransformer, Request, WrappedRequest} + +import scala.concurrent.{Future, ExecutionContext} +import scala.util.Try +import org.bson.types.ObjectId + +import pdi.jwt.{JwtJson, JwtAlgorithm, JwtClaim} +import play.api.libs.json.Json + +import javax.inject.Inject + + +class AuthenticationRequest[A](val userId: Option[ObjectId], request: Request[A]) extends WrappedRequest[A](request) + + +/** + * The authentication action transformer that transforms the incoming base request to a user request. + */ +class AuthenticationTransformer @Inject() (implicit val executionContext: ExecutionContext) extends ActionTransformer[Request, AuthenticationRequest] { + + /** + * Transforms the existing request from a Request to UserRequest. + * + * @param request The incoming request. + * @return The new parameter to pass to the Action block. + */ + override def transform[A](request: Request[A]) = Future.successful { + println(request) + val userId: Option[ObjectId] = processJWT(request) + new AuthenticationRequest(userId, request) + } + + /** + * Processes the JWT token by decoding and validating it. + * + * @param request The incoming request. + * @return The user ID specified in the JWT's payload. + */ + def processJWT[A](request: Request[A]): Option[ObjectId] = { + val privateKey = ConfigFactory.load().getString("jwt.privateKey") + + try { + val authHeader = request.headers.get("Authorization").get + val token = authHeader.substring(7) + println(s"JWT Token Received: $token") + + val payload: Try[JwtClaim] = JwtJson.decode(token, privateKey, Seq(JwtAlgorithm.HS256)) + + val content = payload.get.content + val jsonContent = Json.parse(content) + val userId = (jsonContent \ "userId").as[String] + + Some(new ObjectId(userId)) + } + catch { + case ex: Throwable => { + println(ex) + None + } + } + } +} diff --git a/backend-services/feed-service/build.sbt b/backend-services/feed-service/build.sbt index e42fe9c6..8ac4696d 100644 --- a/backend-services/feed-service/build.sbt +++ b/backend-services/feed-service/build.sbt @@ -20,3 +20,4 @@ libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0 libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "4.3.0" libraryDependencies += "com.typesafe.play" %% "play-ws" % "2.8.10" libraryDependencies += "com.typesafe.play" %% "play-ahc-ws-standalone" % "2.1.10" +libraryDependencies += "com.github.jwt-scala" %% "jwt-play-json" % "9.2.0" diff --git a/backend-services/feed-service/conf/application.conf b/backend-services/feed-service/conf/application.conf index 21793f26..cd190de6 100644 --- a/backend-services/feed-service/conf/application.conf +++ b/backend-services/feed-service/conf/application.conf @@ -2,3 +2,6 @@ mongodb.uri="mongodb://localhost:27017/" mongo.feedService.db = "feed-service" mongo.dailies.collection = "dailies" + +# JWT Authenticationn +jwt.privateKey = "" -- GitLab