OAuth remote request failure due to missing content length
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
- Create an
OAuthobject, then callOAuth#retrieveRequestToken(callbackURL: String). - Method performs a request to remote OAuth server and returns an
Either[OAuthException, RequestToken]containing the result or an exception. - Assuming the OAuth parameters are correct, the
Eithershould contain a validRequestToken. - The token itself is extracted from the
RequestTokenobject to be used in subsequent steps of the OAuth handshake.
Actual Behavior
- Create an
OAuthobject, then callOAuth#retrieveRequestToken(callbackURL: String). - Method returns an
Either[OAuthException, RequestToken]. - The OAuth parameters are correct, yet the
Eithercontains anOAuthException: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
}
}
}