viva-con-agua / play2-oauth-client Edit

Implements an OAuth2 client for Play 2 apps. It allows to use Drops as social provider and tailors the OAuth2 handshake to specific organizational support.

Version Matrix

play2-oauth-client

A Play2 module implementing a small extension to the silhouette authorization library. Using the modules developers are able to connect to an OAuth2 provider that has been implemented to fit the needs of Viva con Agua.

Implements an OAuth2 client for Play2 apps. It allows to use Drops as social provider and tailors the OAuth2 handshake to specific organizational support. First, a user has to be logged out if and only if, a user has been logged out from Drops. Thus play2-oauth-client implements an additional Object-Event-System (OES) using the nats message broker.

Usage

Resolve dependencies Play 2.7.x

Resolve the library from Sonatype in your build.sbt:

resolvers ++= Seq(
  Resolver.sonatypeRepo("public"),
  Resolver.bintrayRepo("scalaz", "releases")
)

libraryDependencies += "org.vivaconagua" %% "play2-oauth-client" % "0.4.7-play27"
// it's important to handle this by your application while play2-oauth-client is using scala_nats
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.8.1"

Resolve dependencies Play 2.5.x and 2.6.x

Resolve the library from Sonatype in your build.sbt:

resolvers += "Sonatype OSS Releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2"

libraryDependencies += "org.vivaconagua" %% "play2-oauth-client" % "0.4.6-play25"

Implement a controller

You have to use the lib in a controller. You can simply implement the DropsLoginController trait:

package controllers

import javax.inject.Inject
import com.mohiva.play.silhouette.api.Silhouette
import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
import com.mohiva.play.silhouette.impl.providers.SocialProviderRegistry
import org.vivaconagua.play2OauthClient.silhouette.CookieEnv
import org.vivaconagua.play2OauthClient.controller.DropsLoginController
import org.vivaconagua.play2OauthClient.silhouette.UserService
import concurrent.ExecutionContext.Implicits.global
import play.api.mvc._
import play.api.libs.ws._
import play.api._
import play.api.cache.CacheApi

class DropsController @Inject()(
                                ws: WSClient,
                                override val conf : Configuration,
                                cc: ControllerComponents,
                                override val silhouette: Silhouette[CookieEnv],
                                override val userService: UserService,
                                override val authInfoRepository: AuthInfoRepository,
                                override val socialProviderRegistry: SocialProviderRegistry,
                                cache: CacheApi
                              ) extends AbstractController(cc) with DropsLoginController {
  override val defaultRedirectUrl = routes.HomeController.index.url // defines the default page a user sees after login 
}

Now you have to add the following routes to your conf/routes file. These routes are needed to redirect the users client in order to fulfill the OAuth2 handshake with the social provider Drops:

GET        /authenticate/:provider  controllers.DropsController.authenticate(provider, route: Option[String], ajax: Option[Boolean])
POST       /authenticate/:provider  controllers.DropsController.authenticate(provider, route: Option[String], ajax: Option[Boolean])

WebApps

Furthermore, if you implement a JavaScript WebApp, you can add the following route to your routes file:

GET        /identity                controllers.DropsController.frontendLogin

If your user has a valid session with Drops, you will receive:

{
  "uuid": "<your-users-uuid>"
}

Otherwise, you will receive a JSON encoded error message using the following format:

{
  "http_error_code": 401,
  "internal_error_code": "401.OAuth2Server",
  "msg": "Currently, there is no authenticated user.",
  "msg_i18n": "error.oauth2.not.authenticated",
  "additional_information": {
    "oauth2_client": "<a microservice identifier>"
  }
}

Auth OES

Since play2-oauth-client has to communicate with your nats message broker and Drops you have to add the following lines to your conf/application.conf:

nats.endpoint="nats://<nats_ip>:<nats_port>" // default port is 4222

ms.name="<your_ms_name>" // example: BLOOB
ms.host="<your_ms_domain>" // example: http://localhost:9000
ms.entrypoint="<your_ms_route>" // the route that you have configured before, example: /authenticate/drops
drops.url.base="<drops_domain>" // example: http://localhost:9100 or https://pool.vivaconagua.org/drops
drops.client_id="<your_ms_id>" // the id that has been configured in drops to identify your microservice
drops.client_secret="<your_ms_secret>" // the secret that has been configured in drops to identify your microservice

play.filters.enabled += org.vivaconagua.play2OauthClient.drops.AuthOESFilter

Example implementation

An example controller using the implemented authentification:

import javax.inject.Inject

import play.api.mvc._
import com.mohiva.play.silhouette.api.Silhouette
import org.vivaconagua.play2OauthClient.silhouette.CookieEnv
import org.vivaconagua.play2OauthClient.silhouette.UserService

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import play.api.mvc.AnyContent
import play.api.Logger

/**
  * A very small controller that renders a home page.
  */
class HomeController @Inject()(
                                cc: ControllerComponents,
                                silhouette: Silhouette[CookieEnv],
                                userService: UserService
) extends AbstractController(cc) {

  val logger: Logger = Logger(this.getClass())

  def userTest = silhouette.SecuredAction.async { implicit request => {
    Future.successful(Ok("User: " + request.identity))
  }}
}

Authorization

Currently, a role-based access control (RbAC) is implemented for the Pool². Using it requires basic knowledge about the designed roles:

  • Supporter: Everyone is a supporter.
  • Employee: Everyone who is employee of Viva con Agua (e.V, Wasser, Goldeimer, Stiftung or Arts create Water).
  • Admin: Group with controling rights for the technical system Pool².
  • Volunteer Manager: All the supporter volunteering as managers of their crew. Thus, the group of users can be separated by their crews, but also by their area of responsability (so called pillar). There are four areas of responsibility: finance, operation, education and network.

The package org.vivaconagua.play2OauthClient.drops.authorization allows you to use RbAC to secure your endpoints:

def getAll = silhouette.SecuredAction(
 (IsVolunteerManager() && IsResponsibleFor("finance")) || IsEmployee || IsAdmin
).async { request => 
    ...
}

Only volunteer manager with the responsability for finance, employees and admins have access rights for getAll. Additionally, you can pass the crews UUID or name to the VolunteerManager() instance. Thus, an action will be accessable only for employees, admins and Volunteer Managers of the given crew.

Hows it works

Todo

ChangeLog

Version 0.4.1 (2018-07-13)