play-ws icon indicating copy to clipboard operation
play-ws copied to clipboard

OAuth remote request failure due to missing content length

Open aksiksi opened this issue 8 years ago • 0 comments

Hello!

The OAuth implementation/wrapper provided with Play WS fails when a request for a token is made (i.e., the first step in the OAuth handshake). The remote server (Evernote in this case) returns a content length missing error. Details below.

Play WS Version

The version bundled with Play 2.6.10 (looks like 1.1.3 based on this).

API

Scala

Operating System

macOS/OS X 10.11.3 (El Capitan)

JDK

java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)

Library Dependencies

N/A

Expected Behavior

  1. Create an OAuth object, then callOAuth#retrieveRequestToken(callbackURL: String).
  2. Method performs a request to remote OAuth server and returns an Either[OAuthException, RequestToken] containing the result or an exception.
  3. Assuming the OAuth parameters are correct, the Either should contain a valid RequestToken.
  4. The token itself is extracted from the RequestToken object to be used in subsequent steps of the OAuth handshake.

Actual Behavior

  1. Create an OAuth object, then callOAuth#retrieveRequestToken(callbackURL: String).
  2. Method returns an Either[OAuthException, RequestToken].
  3. The OAuth parameters are correct, yet the Either contains an OAuthException: OAuthCommunicationException: Communication with the service provider failed: Service provider responded in error: 411 (Length Required).

After some research, I was able to determine that this issue is quite common with OAuth 1.0 implementations (examples: 1, 2).

The suggested fix is to include a Content-Length header set to 0 with the HTTP POST to the remote OAuth endpoint. An even better approach is to just perform a plain GET request instead.

However, Play WS does not provide a way to pass in arbitrary HTTP headers along with the request. Also, at a glance, it seems like Signpost does not provide this option either.

Reproducible Test Case

package controllers

import javax.inject.Inject

import play.api.libs.oauth._
import play.api.mvc._

class EvernoteAuthController @Inject() (cc: ControllerComponents) extends AbstractController(cc) {
  val KEY = ConsumerKey("YOUR_KEY", "YOUR_SECRET")

  val EVERNOTE = OAuth(
    ServiceInfo(
      "https://sandbox.evernote.com/oauth",
      "https://sandbox.evernote.com/oauth",
      "https://sandbox.evernote.com/OAuth.action",
      key = KEY
    ),

    use10a = false
  )

  val CALLBACK_URL = "http://localhost:9000/auth"

  def authenticate = Action { request: RequestHeader =>
    // Step 1: Request temporary token
    EVERNOTE.retrieveRequestToken(CALLBACK_URL) match {
      case Right(t: RequestToken) =>
        // Step 2: Request user authorization; pass temporary token from Step 1
        // Also, store temporary token and secret for later use
        Redirect(EVERNOTE.redirectUrl(t.token)).withSession("token" -> t.token, "secret" -> t.secret)

      // TODO: check this out!
      case Left(e) => throw e
    }
  }
}

aksiksi avatar Jan 03 '18 19:01 aksiksi