Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wrap my routes in an authentication directive that then gives visibility to a custom class for my routes to access

My API clients will pass the session token in the header or query string like:

Http Header with key/value like MyApp-Token abc123
Url:
  https://api.example.com/v1/board?authToken=abc123

 val secureRoutes =
        authenticateToken() { authenticatedContext =>
           path("board") {
             get {
               complete(s"board#index route ${authenticatedContext.user.username}")
             }
           }
       }

Is there a built-in directive that can do this for me or do I have to somehow create my own custom directive that will first look in the HTTP headers, and if not there then look to see if there is a query string value at the key authToken ?

If I have to create a custom directive, any examples I can follow or learn from?

I know I can get a HTTP header using the built-in directive:

          headerValueByName("x-authToken") { authToken =>
            get {
              complete(s"board#index route 2.1 $authToken")
            }
          }

And there is also a directive to get the value from a query string:

  parameter("authToken") { authToken => 

  ...
  }

How could I combine both of them, and then internally I would want to make a database call, and then instead of returning authToken I want to return a custom case class that will contain data I just loaded from the database like:

case class AuthenticatedContext(authToken: String, user: User, ...)
like image 239
Blankman Avatar asked Dec 06 '21 23:12

Blankman


1 Answers

We can always construct a custom directive or compose the directives as shown below:

case class AuthenticatedContext(authToken: String)

  def validateTokenAndGetAuthContext(token: String): Future[AuthenticatedContext] = {
    Future.successful(AuthenticatedContext(token))
  }

  val authToken: Directive1[AuthenticatedContext] = optionalHeaderValueByName("authToken").flatMap {
    case Some(token) =>
      onComplete(validateTokenAndGetAuthContext(token)).flatMap {
        case Failure(_) => reject(AuthorizationFailedRejection)
        case Success(value) => provide(value)
      }
    case None =>
      parameter("authToken".optional).flatMap {
        case Some(token) =>
          onComplete(validateTokenAndGetAuthContext(token)).flatMap {
            case Failure(_) => reject(AuthorizationFailedRejection)
            case Success(value) => provide(value)
          }
        case None => reject(AuthorizationFailedRejection)
      }
  }

It's constructed using HeaderDirectives, ParameterDirectives and FuturesDirectives.

like image 92
Shankar Shastri Avatar answered Oct 07 '22 06:10

Shankar Shastri