I'm something of a beginner with the Play Framework (2.5 and Scala in this case) - and I'm trying to learn by building a bot for Facebook messenger. However I've gotten stuck trying to verify the signature of the messages.
I've followed the Facebook documentation and created a webhook. Which handles POST requests usinggetRawMessages (see code below). This then tries to verify that the request is signed by Facebook using the verifyPayload function. However I can't seem to get the computed and the actual hashes to match up.
I've taken a lead from looking at this question: How to verify Instagram real-time API x-hub-signature in Java? which seems to be doing pretty much what I want, but for the Instagram equivalent. But I still can't seem to get it right.
val secret = "<facebooks secret token>"
def getRawMessages = Action (parse.raw) {
request =>
val xHubSignatureOption = request.headers.get("X-Hub-Signature")
try {
for {
signature <- xHubSignatureOption
rawBodyAsBytes <- request.body.asBytes()
} yield {
val rawBody = rawBodyAsBytes.toArray[Byte]
val incomingHash = signature.split("=").last
val verified = verifyPayload(rawBody, secret, incomingHash)
Logger.info(s"Was verified? $verified")
}
Ok("Test")
}
catch {
case _ => Ok("Test")
}
}
val HMAC_SHA1_ALGORITHM = "HmacSHA1"
def verifyPayload(payloadBytes: Array[Byte], secret: String, expected: String): Boolean = {
val secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA1_ALGORITHM)
val mac = Mac.getInstance(HMAC_SHA1_ALGORITHM)
mac.init(secretKeySpec)
val result = mac.doFinal(payloadBytes)
val computedHash = Hex.encodeHex(result).mkString
Logger.info(s"Computed hash: $computedHash")
computedHash == expected
}
The Facebook webhook docs state:
The HTTP request will contain an X-Hub-Signature header which contains
the SHA1 signature of the request payload, using the app secret as the
key, and prefixed with sha1=. Your callback endpoint can verify this
signature to validate the integrity and origin of the payload
Please note that the calculation is made on the escaped unicode
version of the payload, with lower case hex digits. If you just
calculate against the decoded bytes, you will end up with a different
signature. For example, the string äöå should be escaped to
\u00e4\u00f6\u00e5.
I'm guessing that what I'm missing is getting the payload properly escaped to unicode, but I can't seem to find a way to do it. And the answer in the referenced question also appeared to just get the byte array without doing anything more with it (jsonRawBytes = jsonRaw.asBytes();).
Any help with how to proceed would be much appreciated.
Turns out I was using the wrong secret all along. Should anyone else make the same mistake, please note that it "App Secret" that is available on the application dashboard that you want. See the screenshot below.
Related
I'm trying to write middleware that would extract specific cookie and store information in ContextRequest.
Here is my test code:
def cookie[F[_]: Sync](
logger: Logger[F]
): Kleisli[F, Request[F], ContextRequest[F, Option[Cookie]]] =
Kleisli { request: Request[F] =>
for {
_ <- logger.debug(s"finding cookie")
_ <- logger.debug(request.cookies.map(_.name).mkString(","))
} yield ContextRequest(none[Cookie], request)
}
Then I use it like this:
def httpApp: HttpApp[F] = cookie(logger).mapK(OptionT.liftK).andThen(routesWithCookieContext).orNotFound
The problem is: request doesn't have any cookies even so I see them in the Chrome dev tools and in the request's details in the logs. What I'm doing wrong and how to make it work?
Turned out it was the problem with a cookie content. I was using Circle's .asJson.noSpaces to convert case class into string and write it into cookie's value. But for some reason cookies with json in their value doesn't work.
I'm using tornado and trying to get a facebook user's email address from the Graph API. I have the following code (most of which is from the Tornado website)
class FacebookAuth2Handler(BaseHandler,tornado.auth.FacebookGraphMixin):
#tornado.gen.coroutine
def get(self):
if self.get_argument("code", False):
user = yield self.get_authenticated_user(redirect_uri=self.settings["facebook_redirect_uri"],
client_id=self.settings["facebook_app_id"],
client_secret=self.settings["facebook_secret"],
code=self.get_argument("code"))
ob = yield self.facebook_request("/me/email",access_token=user["access_token"])
print(ob)
else:
yield self.authorize_redirect(redirect_uri=self.settings["facebook_redirect_uri"],
client_id=self.settings["facebook_app_id"],
extra_params={"scope": ["email","public_profile"]})
The problem seems to be fetching the /me/email with the facebook_request() this crashes with the following:
tornado.auth.AuthError: Error response HTTP 400: Bad Request fetching https://graph.facebook.com/me/email?access_token=xxxxxxx
Setting the path to "/me/email" is not valid, and setting it to "/me?fields=email" causes it to send your url as "/me?fields=email?access_token=xxxxxxx", which is no good either.
use the fields parameter:
ob = yield self.facebook_request(
path="/me",
access_token=user["access_token"],
fields="email,gender"
)
or you can really simplify things by adding the extra_fields parameter to get_authenticated_user. Note it is a python list, not a comma-separated string like above:
user = yield self.get_authenticated_user(redirect_uri=self.settings["facebook_redirect_uri"],
client_id=self.settings["facebook_app_id"],
client_secret=self.settings["facebook_secret"],
code=self.get_argument("code"),
extra_fields=['email','gender']
)
Any missing or unpermitted fields will show as None in the returned user mapping object.
Looking for a good explanation on how to do authentication using akka HTTP. Given a route that looks like
val route =
path("account") {
authenticateBasic(realm = "some realm", myAuthenticator) { user =>
get {
encodeResponseWith(Deflate) {
complete {
//do something here
}
}
}
}
}
The documentation outlines a way, but then the pertinent part performing the actual authentication is omitted...
// backend entry points
def myAuthenticator: Authenticator[User] = ???
Where can I find an example implementation of such an authenticator? I have the logic already for authenticating a user given a user name and password, but what i can't figure out is how to get a username/password (or token containing both) from the HTTP request (or RequestContext).
Authenticator is just a function UserCredentials => Option[T], where UserCredentials in case of being (check with pattern matching) Provided have verifySecret(secret) method which you need to safely call and return Some (Some user for example) in case of success, like:
def myAuthenticator: Authenticator[User] = {
case p#Provided(username) =>
if(p.verifySecret(myGetSecret(username))) Some(username) else None
case Missing => None //you can throw an exeption here to get customized response otherwise it will be regular `CredentialsMissing` message
}
myGetSecret is your custom function which gets username and returns your secret (e.g. password), getting it possibly from database. verifySecret will securely compare (to avoid timing attack) provided password with your password from myGetSecret. Generally, "secret" is any hidden information (like hash of credentials or token) but in case of basic authentication it is just a plain password extracted from http headers.
If you need more customized approach - use authenticateOrRejectWithChallenge that gets HttpCredentials as an input, so you can extract provided password from there.
More info about authorization is in scaladocs.
I'm adding CSRF protection to my web site (implemented with Scala Play 2.8). It appears to be working from the browser. That is, I can display a protected page in a browser, change the value of the CSRF token using the browser's tools, submit, and I then get the error message generated by Play at line 131 in CSRFActions.scala.
The problem is how to set up a test to verify this in my test suite.
Here's my test:
class AsisCtrlUpdatePrefsSpec extends OatSpec with OneAppPerSuite {
implicit override lazy val app: FakeApplication = FakeApplication(
withGlobal = Some(oat.wapp.GlobalWApp))
"Getting a preferences form should" - {
"detect CSRF tampering" in new DefaultTestContext("myUser") {
// Get a valid token; create a tampered version
val token = CSRF.SignedTokenProvider.generateToken
val tamperedToken = token.replace("1", "2").replace("3", "4")
assert(token != tamperedToken)
// Build a request object with info to get it authenticated,
// valid data for the posted form, and the tampered csrfToken.
// Include a session with the correct csrfToken
implicit val postRequest = FakeRequest(POST,
routes.AsisCtrl.updatePreferences("advisor").url)
.withAuthenticator(this.identity.loginInfo)
.withFormUrlEncodedBody("col1Prefixes" → "CS", "col2Prefixes" → "MATH",
"acadRecordSort" → "Ascending", "notesSort" → "Ascending",
"csrfToken" → tamperedToken)
.withSession("csrfToken" → token)
// This is what the application should do when a VALID
// token is presented. But with the invalid token, this
// should fail. It doesn't.
val result2 = this.asisCtrl.updatePreferences("advisor")(postRequest)
assertResult(SEE_OTHER) {status(result2)}
assertResult(routes.AsisCtrl.getPreferences.url) {
await(result2).header.headers.get("Location").get
}
}
}
}
OatSpec is a trait the contains frequently used stuff from ScalaTest + Play.
GlobalWApp is the global settings object for this sub-module. withAuthenticator provides the needed information to authenticate the user "myUser"
I'm satisfied that it works correctly from the browser. I'd appreciate help in figuring out what's wrong with my test.
Thanks!
PS: Related question -- I have a form set up for the data input on this page. Haven't been able to figure out how to use it to avoid the hand-crafted Map in the call to withFormUrlEncodedBody. I think the call to withFormUrlEncodedBody is legitimate. The Play code appears to check headers first for a token with the correct name.
I have code like:
https://gist.github.com/daaatz/7665224
but dont know how to test request.
Trying mydomain/secured?user=John&password=p4ssw0rd etc but nothing works.
Can some one tell me or show example in js+html how to check is it working fine ?
Thanks
I've never used BasicAuth in Spray, so i'm not sure if this would be the complete answer, but i hope this will help you.
At first. in spray there is a great spray-testkit written on top of akka testkit. You should definitely check out SecurityDirectives test on github, this will show you how to test basic authentication. A little example, to make this simpler:
As for your route example, i would better edit to the following one:
val myRoute =
(path("secured") & get) {
authenticate(BasicAuth(myUserPassAuthenticator _, realm = "secure site")) {
userName => complete(s"The user is '$userName'")
}
}
}
Adding get directive will specify that this route expects a Get request and sealRoute is obsolete cause RejectionHandler and ExceptionHandler are provided implicitly with runRoute. It is used only in tests, if you want wo check exceptions/rejections.
Now in your tests you should construct auth entities, similar to the test one:
val challenge = `WWW-Authenticate`(HttpChallenge("Basic", "Realm"))
val doAuth = BasicAuth(UserPassAuthenticator[BasicUserContext] { userPassOption ⇒
Future.successful(Some(BasicUserContext(userPassOption.get.user)))
}, "Realm")
And you test case:
"simple auth test" in {
Get("security") ~> Authorization(BasicHttpCredentials("Alice", "")) ~> {
authenticate(doAuth) { echoComplete }
} ~> check { responseAs[String] === "BasicUserContext(Alice)" }
}
In Get("security") specifies that your test will send a Get request on "/security", then add Authorization to the test request, some action and the check part to test the request.
I've never tried to test BasicAuth, so there could be some mistakes.
I would look into CURL for testing routes in web applications, but I've also used the chrome extension Postman with great results as well.