Skip to content

Controllers (Use)

Angel Sanadinov edited this page Jul 19, 2017 · 7 revisions

Controllers: Basics | Use | Config | Service Requests | Users & Tokens

Overview

Some other useful bits of information:

Note: Implicit parameters and controller dependencies have been removed for brevity.

Client Controllers

Auth0

conf/routes

# Routes

GET     /          controllers.System.root
GET     /test      controllers.System.test
GET     /login     controllers.System.login(code: Option[String])
GET     /logout    controllers.System.logout

Service - controllers/System.scala

import javax.inject.Inject

import core3.config.StaticConfig
import core3.http.controllers.auth0.ClientController
import play.api.cache.SyncCacheApi
import play.api.libs.ws.WSClient

import scala.concurrent.{ExecutionContext, Future}

class System @Inject()(ws: WSClient, cache: SyncCacheApi /*...*/)
  extends ClientController(
    ws,
    cache,
    StaticConfig.get.getConfig(
      "security.authentication.clients.<SOME_AUTH0_CLIENT_NAME>"
    )
  ) {

  def root() = PublicAction(
    { (request, user) => implicit val r = request
      user match {
        case Some(_) => Future.successful(Redirect("/test"))
        case None => Future.successful(Redirect("/login"))
      }
    }
  )

  def test() = AuthorizedAction(
    "test:view",
    okHandler = { (request, user) => implicit val r = request
      //... do some work ...
    }
  )

  def login(codeOpt: Option[String] = None) = LoginAction(
    { implicit request => //success
      Future.successful(Redirect("/"))
    }, { implicit request => //not allowed
      Future.successful(Unauthorized("Authentication failed"))
    }, { implicit request => //should log in
      Future.successful(Ok(views.html.system.login("Test - Login")))
    }
  )

  def logout() = LogoutAction(Some("https://<some host & port>/"))

}

Authentication

Three Auth0 "components" are involved (in a way) in performing user authentication: Clients, Connections and Users (all are configured through Auth0's management page). Before a user can log in, the following conditions need to be met:

  • Connections - an appropriate credentials connection needs to be available and configured
  • Clients - the client needs to exist, be configured and it needs to use the above connection (can be set in Clients -> <SOME_AUTH0_CLIENT_NAME> -> Connections)
  • Users -> the user needs to exist and it has to be associated with the above connection

Authorization / Permissions

The Auth0 Authorization extension needs to be installed and available in Extensions. User authorization is based on the permissions list under Permissions in the extension's management page. For more info on how to configure roles and groups in order to assign permissions to users, check the extension's docs.

Note: Tested with Auth0 Authorization version 2.1

Local

conf/routes

# Routes

GET     /                 controllers.System.root
GET     /public           controllers.System.public
GET     /internal         controllers.System.internal
GET     /system/login     controllers.System.loginPage
POST    /system/login     controllers.System.login
GET     /system/logout    controllers.System.logout

Service - controllers/System.scala

import javax.inject.Inject

import core3.config.StaticConfig
import core3.database.dals.DatabaseAbstractionLayer
import core3.http.controllers.local.ClientController
import core3.http.responses.GenericResult
import play.api.cache.SyncCacheApi

import scala.concurrent.Future

class System @Inject()(cache: SyncCacheApi, db: DatabaseAbstractionLayer /*...*/)
  extends ClientController(
    cache,
    StaticConfig.get.getConfig(
      "security.authentication.clients.<SOME_LOCAL_AUTH_CLIENT_NAME>"
    ),
    db
  ) {

  def root() = PublicAction(
    { (request, user) => implicit val r = request
      user match {
        case Some(_) => Future.successful(Redirect("/internal"))
        case None => Future.successful(Redirect("/public"))
      }
    }
  )

  def public() = PublicAction(
    { (request, user) => implicit val r = request
      //... do some work ...
    }
  )

  def internal() = AuthorizedAction(
    "test:view",
    okHandler = { (request, user) => implicit val r = request
      //... do some work ...
    }
  )

  def loginPage = PublicAction(
    { (request, user) => implicit val r = request
      user match {
        case Some(_) => Future.successful(Redirect("/"))
        case None => Future.successful(Ok("Some login page"))
      }
    }
  )

  //Login action handler
  def login() = LoginAction(
    { implicit request => //success
      Future.successful(Ok(GenericResult(wasSuccessful = true).asJson))
    }, { implicit request => //not allowed
      Future.successful(
        Unauthorized(
          GenericResult(
            wasSuccessful = false,
            message = Some(s"Invalid user and/or password")
          ).asJson
        )
      )
    }, { implicit request => //should log in
      Future.successful(
        Unauthorized(
          GenericResult(
            wasSuccessful = false,
            message = Some(s"Login required")
          ).asJson
        )
      )
    }
  )

  //Logout action handler
  def logout() = LogoutAction() //use default
}

Users

During development it can be useful to define a test that initializes your credentials store (DB) with some test users. You can see an example here. At least one LocalUser (with type Client) will be needed.

Service Controllers

Auth0

When using Auth0 as the auth provider, having multiple routes is only possible by having multiple controllers, as the API configuration is bound to the route (API Identifier). Below is a simple example implementation with two routes/services.

Note: The naming of the routes, APIs, clients, controllers and actions is entirely up to you; this is just an example.

conf/routes

# Routes
POST    /service/users      controllers.UsersService.core
POST    /service/clients    controllers.ClientsService.core

Two APIs are needed:

  • <SOME_AUTH0_API_NAME_FOR_CLIENT_SERVICE> with API Audience == https://<some host & port>/service/clients
  • <SOME_AUTH0_API_NAME_FOR_USER_SERVICE> with API Audience == https://<some host & port>/service/users

Auth0 management - Clients

Two non-interactive clients are needed (if they do not exist):

  • <SOME_AUTH0_API_NAME_FOR_CLIENT_SERVICE>
  • <SOME_AUTH0_API_NAME_FOR_USER_SERVICE>

Client-Aware service - controllers/ClientService.scala

import javax.inject.{Inject, Singleton}

import core3.config.StaticConfig
import core3.http.controllers.auth0.ServiceController
import play.api.mvc._
import play.api.cache.SyncCacheApi
import play.api.libs.ws.WSClient

@Singleton
class ClientsService @Inject()(ws: WSClient, cache: SyncCacheApi /*...*/)
  extends ServiceController(
    ws,
    cache,
    StaticConfig.get.getConfig(
      "security.authentication.services.<SOME_AUTH0_API_NAME_FOR_CLIENT_SERVICE>"
    ),
    StaticConfig.get.getConfig(
      "security.authentication.clients.<SOME_AUTH0_API_NAME_FOR_CLIENT_SERVICE>"
    )
  ) {

  def core() = ClientAwareAction(
    "exec:any-workflow", //see `Scopes` below
    (request: Request[AnyContent], clientID: String) => {
      //... do some work ...
    }
  )

}

User-Aware service - controllers/UserService.scala

import javax.inject.{Inject, Singleton}

import core3.config.StaticConfig
import core3.http.controllers.auth0.ServiceController
import core3.security.UserTokenBase
import play.api.mvc._
import play.api.cache.SyncCacheApi
import play.api.libs.ws.WSClient

@Singleton
class UsersService @Inject()(ws: WSClient, cache: SyncCacheApi /*...*/)
  extends ServiceController(
    ws,
    cache,
    StaticConfig.get.getConfig(
      "security.authentication.services.<SOME_AUTH0_API_NAME_FOR_USER_SERVICE>"
    ),
    StaticConfig.get.getConfig(
      "security.authentication.clients.<SOME_AUTH0_API_NAME_FOR_USER_SERVICE>"
    )
  ) {

  def core() = UserAwareAction(
    "exec:workflow", //see `Scopes` below
    (request: Request[AnyContent], user: UserTokenBase) => {
      //... do some work ...
    }
  )

}

Scopes

Auth0 scopes must be created for an API via Auth0 management -> APIs -> <SOME_AUTH0_API_NAME> -> Scopes. After that is done, a client that needs to interact with the service needs to be allowed to use those scopes and the API: Auth0 management -> APIs -> <SOME_AUTH0_API_NAME> -> Non Interactive Clients -> select Authorized for the appropriate client -> expand subsection -> Scopes -> select the appropriate scopes

Local

conf/routes

# Routes
POST    /service/users      controllers.Service.users
POST    /service/clients    controllers.Service.clients

Users

During development it can be useful to define a test that initializes your credentials store (DB) with some test users. You can see an example here. At least one LocalUser (with type Service) will be needed for client-aware actions and at least two LocalUsers (one with type Service and one with type Client) will be needed for user-aware actions.

Service - controllers/Service.scala

import javax.inject.{Inject, Singleton}

import core3.config.StaticConfig
import core3.database.dals.DatabaseAbstractionLayer
import core3.http.controllers.local.ServiceController
import core3.security.UserTokenBase
import play.api.cache.SyncCacheApi
import play.api.mvc._

import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._

@Singleton
class Service @Inject()(db: DatabaseAbstractionLayer, cache: SyncCacheApi /*...*/)
  extends ServiceController(
    cache,
    StaticConfig.get.getConfig(
      "security.authentication.clients.<SOME_LOCAL_AUTH_SERVICE_NAME>"
    ),
    db
  ) {

  def users() = UserAwareAction(
    "exec:asUser",
    (request: Request[AnyContent], user: UserTokenBase) => {
      //... do some work ...
    }
  )

  def clients() = ClientAwareAction(
    "exec:asClient",
    (request: Request[AnyContent], clientID: String) => {
      //... do some work ...
    }
  )

}

Scopes

The local auth scopes map directly to a LocalUser's permissions field. So if a service user needs to access an action that requires exec:asUser, it will need to be in that user's permissions list.

Clone this wiki locally