Chapter 11. Web Services and Authentication

The internet is vast and constantly expanding. A lot of day-to-day tasks can be dealt with in a simpler manner—bill payments, checking reviews of a product, booking movie tickets, and so on. In addition to this, most electronic devices can now be connected to the Internet, such as mobile phones, watches, surveillance systems, and security systems. These can communicate with each other and they need not all be of the same brand. Applications can utilize user-specific information and provide features with better customization. Most importantly, we can decide if we wish to share our information with the application by authenticating it or not.

In this chapter, we will cover Play Framework's support for the following:

  • Calling web services
  • OpenID and OAuth authentication

Calling web services

Suppose we need to book a flight ticket online. We can do this by using either the website of the flight's brand (such as Lufthansa, Emirates, and so on), or a travel booking website (such as ClearTrip, MakeMyTrip, and so on). How is it that we can do the same task from two or more different websites?

The website of the flight's brand provides some APIs with which the travel booking websites work. These API can be freely available or charged by a contract, which is for the provider and the other third-party involved to decide. These APIs are also called web services.

A web service is more or less a method that is called over the Internet. Only the provider is fully aware of the internal working of these sites. Those who use the web service are only aware of the purpose and its possible outcome.

Many applications require/prefer to use third-party APIs to complete common tasks for various reasons, such as common norms in the business domain, easier means to provide secure authorization, or to avoid the overhead of maintenance, and so on.

The Play Framework has a web service API specifically to meet such requirements. The web service API can be used by including it as a dependency:

libraryDependencies ++= Seq(
  ws
)

A common use case is to send an e-mail with the link for account verification and/or resetting the password using a transactional e-mail API service, such as Mailgun, SendGrid, and so on.

Let's assume that our application has such a requirement, and we have an Email object that handles all these kind of transactions. We need one method to send e-mails that makes actual calls to the e-mailing API service, and then other methods that internally call send. Using the Play web service API, we could define Email as:

object Email {

  val logger = Logger(getClass)

  private def send(emailIds: Seq[String], subject: String, content: String): Unit = {

    var properties: Properties = new Properties()

    try {

      properties.load(new FileInputStream("/opt/appName/mail-config.properties"))

      val url: String = properties.getProperty("url")

      val apiKey: String = properties.getProperty("api")

      val from: String = properties.getProperty("from")

      val requestHolder: WSRequestHolder = WS.url(url).withAuth("api", apiKey, WSAuthScheme.BASIC)
      val requestData = Map(

        "from" -> Seq(from),

        "to" -> emailIds,

        "subject" -> Seq(subject),

        "text" -> Seq(content))

      val response: Future[WSResponse] = requestHolder.post(requestData)

      response.map(

        res => {

          val responseMsg: String = res.json.toString()

          if (res.status == 200) {

            logger.info(responseMsg)

          } else {

            logger.error(responseMsg)

          }

        }

      )

    } catch {

      case exp: IOException =>

        logger.error("Failed to load email configuration properties.")

    }

  }

  def sendVerification(userId: Long, emailId: String, host: String): Unit = {

    val subject: String = "Email Verification"

    val content: String =

      s"""To verify your account on <appName>, please click on the link below

         |

         |http://$host/validate/user/$userId""".stripMargin

    send(Seq(emailId), subject, content)

  }

  def recoverPassword(emailId: String, password: String): Unit = {

    val subject: String = "Password Recovery"

    val emailContent: String = s"Your password has been reset.The new password is $password"

    send(Seq(emailId), subject, emailContent)

  }

}

The web service API is exposed through the WS object, which provides methods to query web services as an HTTP client. In the preceding code snippet, we have used the web service API to make a post request. Other available methods to trigger a request and fetch a response or response stream are:

  • get or getStream
  • put or putAndRetrieveStream
  • post or postAndRetrieveStream
  • delete
  • head

The result of any of these calls is of the Future[WSResponse] type, so we can safely say that the web service API is asynchronous.

It is not restricted to REST services. For example, let's say we use a SOAP service to fetch the currencies of all countries:

  def displayCurrency = Action.async {

    val url: String = "http://www.webservicex.net/country.asmx"

    val wsReq: String = """<?xml version="1.0" encoding="utf-8"?>

                          |<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">

                          |  <soap12:Body>

                          |    <GetCurrencies xmlns="http://www.webserviceX.NET" />

                          |  </soap12:Body>

                          |</soap12:Envelope>""".stripMargin

    val response: Future[WSResponse] = WS.url(url).withHeaders("Content-Type" -> "application/soap+xml").post(wsReq)

    response map {

      data => Ok(data.xml)

    }

  }

An HTTP request can be built using WS.url(), which returns an instance of WSRequestHolder. The WSRequestHolder trait has methods to add headers, authentication, request parameters, data, and so on. Here is another example of commonly used methods:

WS.url("http://third-party.com/service?=serviceName")
.withAuth("api","apiKey", WSAuthScheme.BASIC)
.withQueryString("month" -> "12",
        "year" -> "2014",
        "code" -> "code")
.withHeaders(HeaderNames.ACCEPT -> MimeTypes.JSON)
.get

Although in this example we have used Basic authentication, the web service API supports most of the commonly used authentication schemes, which you can find at the following links:

All the methods available through the WS object simply call the relevant methods of the available WSAPI trait's implementation. The web service API provided by default utilizes Ning's AysncHttpClient (refer to https://github.com/AsyncHttpClient/async-http-client). If we wish to use any other HTTP client, we need to implement the WSAPI trait and bind it through a plugin. When we add the ws Play library, it adds play.api.libs.ws.ning.NingWSPlugin to our application, which is defined as:

class NingWSPlugin(app: Application) extends WSPlugin {

  @volatile var loaded = false

  override lazy val enabled = true

  private val config = new DefaultWSConfigParser(app.configuration, app.classloader).parse()

  private lazy val ningAPI = new NingWSAPI(app, config)

  override def onStart() {
    loaded = true
  }

  override def onStop() {
    if (loaded) {
      ningAPI.resetClient()
      loaded = false
    }
  }

  def api = ningAPI

}

Note

In a Play app, using SSL with WS requires a few changes in the configuration, and it is documented at https://www.playframework.com/documentation/2.3.x/WsSSL.

Since a huge number of applications rely on a user's data from various sources, Play provides an API for OpenID and OAuth. We will discuss these in the following sections.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.249.210