question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Provide API to validate HmacSHA256 signatures on incoming payloads

See original GitHub issue

Discussed in https://github.com/orgs/playframework/discussions/11489

<div type='discussions-op-text'>

Originally posted by phelps-sg October 16, 2022

I have also discussed this issue on StackOverflow (https://stackoverflow.com/questions/73926931/in-scala-play-framework-is-there-a-simple-way-to-verify-signatures-on-form-requ), and the only answer so far suggests that there is currently no straightforward way to validate Hmac signatures when processing form data using the Play Framework.

I am trying to write a Scala Play Framework action that will verify a HmacSHA256 signature on an incoming POST request containing form-url-encoded data.

This is not straightforward in the Play framework because: i) actions builders only have access to headers, but do not have access to the request body, and ii) in order to calculate the signature we have to treat the request body as Array[ByteString], but when we come to process the form data we have to treat it as Map[String, Seq[String]], the problem being that Play forces us to choose a single type for our request, and we cannot easily “cast” the request body to a different type.

The only solution I have been able to come up with is to use an ActionRefiner that returns a WrappedRequest that embeds a callback to validate the signature. The callback in turn reparses the data using FormUrlEncodedParser.parse(new String(request.body.toArray)). This approach is illustrated in the code below. However, this is overly convoluted. It would be a lot nicer if the ability to validate hmac signatures on incoming payloads was facilitated directly by the Play Framework API.

package actions

import akka.util.ByteString
import com.google.inject.Inject
import play.api.Logging
import play.api.mvc.Results.Unauthorized
import play.api.mvc._
import play.core.parsers.FormUrlEncodedParser
import services.SlackSignatureVerifierService

import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try

class SlackRequest[A](
    val validateSignature: String => Try[String],
    request: Request[A]
) extends WrappedRequest[A](request)

object SlackSignatureVerifyAction {

  implicit class SlackRequestByteStringValidator(
      slackRequest: SlackRequest[ByteString]
  ) {
    def validateSignatureAgainstBody(): Try[Map[String, Seq[String]]] = {
      val raw = slackRequest.body.utf8String
      slackRequest.validateSignature(raw) map { _ =>
        FormUrlEncodedParser.parse(new String(slackRequest.body.toArray))
      }
    }
  }

  val HEADERS_TIMESTAMP: String = "X-Slack-Request-Timestamp"
  val HEADERS_SIGNATURE: String = "X-Slack-Signature"
}

class SlackSignatureVerifyAction @Inject() (
    val parser: BodyParsers.Default,
    slackSignatureVerifierService: SlackSignatureVerifierService
)(implicit ec: ExecutionContext)
    extends ActionBuilder[SlackRequest, AnyContent]
    with ActionRefiner[Request, SlackRequest]
    with Logging {

  override protected def executionContext: ExecutionContext = ec

  override protected def refine[A](
      request: Request[A]
  ): Future[Either[Result, SlackRequest[A]]] = {

    val timestamp =
      request.headers.get(SlackSignatureVerifyAction.HEADERS_TIMESTAMP)

    val signature =
      request.headers.get(SlackSignatureVerifyAction.HEADERS_SIGNATURE)

    (timestamp, signature) match {
      case (Some(timestamp), Some(signature)) =>
        Future.successful {
          val validate = (body: String) =>
            slackSignatureVerifierService.validate(timestamp, body, signature)
          Right(new SlackRequest[A](validate, request))
        }
      case _ =>
        Future { Left(Unauthorized("Invalid signature headers")) }
    }

  }

}

The validation on the controller side looks like this:

  def slashCommand: Action[ByteString] = {
    slackSignatureVerifyAction.async(parse.byteString) { request =>
      logger.debug("received slash command")
      request
        .validateSignatureAgainstBody()
        .map(processForm) match {
        case Success(result) => result
        case Failure(ex) =>
          ex.printStackTrace()
          Future { Unauthorized(ex.getMessage) }
      }
    }
  }

Issue Analytics

  • State:closed
  • Created a year ago
  • Reactions:1
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
phelps-sgcommented, Nov 10, 2022

I have started documenting outstanding issues here: https://github.com/phelps-sg/play-hmac-signatures/issues. If anyone wants to take any of these on give me a shout.

1reaction
wsargentcommented, Nov 10, 2022

I have something that may at least cut down on the signature building for this https://github.com/tersesystems/securitybuilder#macs-signatures-passwords

 MacBuilder.builder().withAlgorithm("HmacSHA256").withKey(key).build();
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to Implement SHA256 Webhook Signature Verification
Using HMAC signature verification to authenticate and validate webhooks · Get the raw body of the request; · Extract the signature header value;...
Read more >
HMAC Authentication in Web API - Dot Net Tutorials
In this article, I am going to discuss how to implement the HMAC Authentication in Web API. It is one of the mechanisms...
Read more >
How to validate a webhook message using HMAC | DocuSign
This topic demonstrates an example workflow for a client app receiving and validating a webhook (DocuSign Connect) message using HMAC security.
Read more >
C# Checking HMAC SHA256 from API - Stack Overflow
I'm trying to validate a hmac sha256 key that an API sends to me in a header. ... with the signature value of...
Read more >
HMAC-SHA256 Signature Verification of Request-Response
The RMS Cloud LM (server) uses HMAC-SHA256-based message signing to authenticate client requests (users/devices consuming licenses) while using ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found