Peter Weyand
Peter Weyand

Reputation: 2259

Decode Cookie String with Scala/Play Framework 2.7.x

Ok.

So, the normal way to decode cookies in scala/play framework is like this:

 def admin_scrape[T]:Action[AnyContent] = Action.async {request: Request[AnyContent] => Future {
      println("****************************")
      println("inside admin_scrape")
      println("****************************")
      val body: AnyContent          = request.body
      val jsonBody: Option[JsValue] = body.asJson
      var texts_update: String = "";
      var article_url_update: String = "";
      jsonBody
      .map{json =>  
        println("value of json")
        println(json)
        request.session
        .get("access_rights") //access_rights is the token name in the cookie string I set
        .map{access_rights =>
        <...>

However, I've wanted to use sockets in Play.

Unfortunately, the play framework's web socket support (https://www.playframework.com/documentation/2.7.x/ScalaWebSockets) expressly supports flow like structures with the data stream initiating on the front end. Not knocking them, perhaps it was a design choice, but I wanted socketing with requests able to instantiate on the back end as well. So I used this (https://github.com/TooTallNate/Java-WebSocket) to get idiomatic socketing support.

This works.

However, consider the following (it works as one would expect):

override def onOpen(conn: WebSocket, handshake: ClientHandshake): Unit = {
    conn.send("Welcome to the server client!")
    broadcast("new connection everyone: " + handshake.getResourceDescriptor)
    var cookie = "no cookie"
    if(handshake.hasFieldValue("Cookie")){
      println("We found a cookie")
      var cookie = handshake.getFieldValue("Cookie");
      println("value of cookie")
      println(cookie)
      // the following CODE_BLOCK does not work
      // and gives the error:
      // [error] /Users/patientplatypus/Documents/platypusNEST/the_daily_blech/scala_blech_blog/blog/app/sockets/SocketServer.scala:86:32: value decode is not a member of object play.api.mvc.Cookies
      // [error]       val decode_val = Cookies.decode(cookie)
      // [error]                                ^
      // *CODE_BLOCK*
      // val decode_val = play.api.mvc.Cookies.decode(cookie)
      // println("value of decode_val")
      // println(decode_val)
      // *CODE_BLOCK*
      // reasoning - I need a way to get the cookie string into a mapping and this seemed likely.
    }else{
      println("We did not find a cookie")
    }
    <...>

When I run this from an app that has a client side cookie I successfully get printed on the server:

We found a cookie
value of cookie
PLAY_SESSION=SUPERDUPERLONGHASHSTRING

I need to be able to convert this hash string back into a mapping like I could do with requests, but I haven't been able to find the right decoding. An attempt was made above, but I realized I had been reading the documents for play framework 2.0 (https://www.playframework.com/documentation/2.0/api/scala/play/api/mvc/Cookies$.html). I was wondering if some sort of decode function existed in the current 2.7.x version, or how else to convert this hash string back into a cookie map.

EDIT:

The current, most relevant documentation appears to be https://www.playframework.com/documentation/2.7.x/api/scala/play/api/mvc/Cookies$.html, specifically decodeCookieHeader or decodeSetCookieHeader as they take a string and return a sequence of Cookies, according to each methods type signature. However, running

var cookiesDecodeCookieHeader = Cookies.decodeCookieHeader(cookie)
println("value of cookiesDecodeCookieHeader: ")
println(cookiesDecodeCookieHeader)
var cookiesDecodeSetCookieHeader = Cookies.decodeSetCookieHeader(cookie)
println("value of cookiesDecodeSetCookieHeader: ")
println(cookiesDecodeSetCookieHeader)

gives me

value of cookiesDecodeCookieHeader: 
List()
value of cookiesDecodeSetCookieHeader: 
List()

So, this isn't correct.

EDIT EDIT:

Likewise,

var cookiesFromCookieHeader = Cookies.fromCookieHeader(Some(cookie.toString))
  println("value of cookiesFromCookieHeader: ")
  println(cookiesFromCookieHeader)
  var cookiesFromSetCookieHeader = Cookies.fromSetCookieHeader(Some(cookie.toString))
  println("value of cookiesFromSetCookieHeader: ")
  println(cookiesFromSetCookieHeader)

Results in empty maps. At this point my solutions seem to be centered around looking through the man pages (https://www.playframework.com/documentation/2.7.x/api/scala/play/api) at random, so I've opened up an issue for clarification on the play github: (https://github.com/playframework/playframework/issues/9837).

If anyone has any ideas please let me know. Thanks!

EDIT EDIT EDIT:

I've gotten to the point where I'm investigating other JWT libraries, because apparently play's doesn't work the way I need it to :<. I've looked at https://github.com/pauldijou/jwt-scala/, but it doesn't seem to work with the latest version of play (see here: https://github.com/pauldijou/jwt-scala/issues/153). I've also looked at things like this (which seems nice and tidy) https://dzone.com/articles/jwt-authentication-with-play-framework, but the library they used is deprecated.

Wow, this should not be this hard. Any suggestions would be appreciated.

Upvotes: 0

Views: 1238

Answers (2)

Michael Wolfendale
Michael Wolfendale

Reputation: 86

So it looks like in your example of retrieving a value from a cookie you're actually talking about retrieving a session value (request.session.get(...)).

Unfortunately, the play framework's web socket support (https://www.playframework.com/documentation/2.7.x/ScalaWebSockets) expressly supports flow like structures with the data stream initiating on the front end.

I'm not sure what you mean here, it's definitely possible for Play to send the first message in a Websocket communication.

(From the Play websockets documentation)

import play.api.mvc._
import akka.stream.scaladsl._

def socket = WebSocket.accept[String, String] { request =>
  // Just ignore the input
  val in = Sink.ignore

  // Send a single 'Hello!' message and close
  val out = Source.single("Hello!")

  Flow.fromSinkAndSource(in, out)
}

This is a very simple example which will return a "Hello!" message to the websocket client once a connection is established.

This handling code also has direct access to the request: RequestHeader which means that you can just grab things from the session directly from there without having to decode any cookies:

  def ws = WebSocket.accept { request =>

    request.session.get("access_rights") match {
      case Some(value) =>
        println("We found access_rights!")
        println("value of access_rights")
        println(value)
      case None =>
        println("We did not find access_rights!")
    }

    val in = Sink.ignore
    val out = Source.single("Welcome to the server client!")
    Flow.fromSinkAndSource(in, out)
  }

Upvotes: 2

Peter Weyand
Peter Weyand

Reputation: 2259

I managed to figure it out by backing out the Cookies.scala file in the play framework found here (https://github.com/playframework/playframework/blob/2.7.x/core/play/src/main/scala/play/api/mvc/Cookie.scala). This was more difficult than I think it ought to be just to parse the session cookie data (mostly to know where to look) so I didn't have to import random libraries. This is also an example of using java-websocket package which is much more idiomatic than the play framework way. I'm posting the whole file (it's small) so that all the imports and logic can be seen.

If you are attempting to implement this yourself, then you should find the following output:

value of jws.getBody.asScala.toMap: 
Map(data -> {your_cookie_key=your_cookie_value, csrfToken=SUPER-SECRET-LONGHASH}, 
     nbf -> SECRETNUM, iat -> SECRETNUM)

in your terminal.

Thanks again for all those who were willing to help.

package sockets

import play.api.libs.json._
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.net.InetSocketAddress
import java.net.UnknownHostException
import java.nio.ByteBuffer
import org.java_websocket.WebSocket
import org.java_websocket.WebSocketImpl
import org.java_websocket.framing.Framedata
import org.java_websocket.handshake.ClientHandshake
import org.java_websocket.server.WebSocketServer

import javax.inject._
import play.api.libs.json._
import play.api._
import play.api.mvc._
import play.api.http._

import scala.collection.JavaConverters._

import io.jsonwebtoken.Jwts
import io.jsonwebtoken._

import java.nio.charset.StandardCharsets
import java.time.Clock
import java.util.{ Base64, Date, Locale }


@Singleton
class SocketServer @Inject()() extends WebSocketServer(new InetSocketAddress(8887)){


  def parse(encodedString: String): Map[String, AnyRef] = {
    println("inside parse def and value of encodedString: ", encodedString)
    println("***************")
    println("***************")
    println("***************")

    var newEncodedString = encodedString.slice(13, encodedString.length)
    println("value of newEncodedString")
    println(newEncodedString)
    println("***************")
    println("***************")
    println("***************")

    def jwtConfiguration = new JWTConfiguration();

    val jwtClock = new io.jsonwebtoken.Clock {
      val clock = Clock.systemDefaultZone().instant()
      override def now(): Date = java.util.Date.from(clock)
    }

    val base64EncodedSecret: String = {
      Base64.getEncoder.encodeToString(
        "hellotheresailor".getBytes(StandardCharsets.UTF_8) //note that this secret should ideally be set in your application.conf file and imported via injection
      )
    }

    val jws: Jws[Claims] = Jwts.parser()
      .setClock(jwtClock)
      .setSigningKey(base64EncodedSecret)
      .setAllowedClockSkewSeconds(jwtConfiguration.clockSkew.toSeconds)
      .parseClaimsJws(newEncodedString)

    val headerAlgorithm = jws.getHeader.getAlgorithm
    if (headerAlgorithm != jwtConfiguration.signatureAlgorithm) {
      val id = jws.getBody.getId
      val msg = s"Invalid header algorithm $headerAlgorithm in JWT $id"
      throw new IllegalStateException(msg)
    }

    println("value of jws.getBody.asScala.toMap: ")
    println(jws.getBody.asScala.toMap)

    jws.getBody.asScala.toMap
  }

  override def onOpen(conn: WebSocket, handshake: ClientHandshake): Unit = {
    conn.send("Welcome to the server!")
    broadcast("new connection: " + handshake.getResourceDescriptor)
    var cookie = "no cookie"
    if(handshake.hasFieldValue("Cookie")){
      println("We found a cookie")
      var cookie = handshake.getFieldValue("Cookie");
      println("value of cookie")
      println("parsedCookie: ")
      val parsedCookie = parse(cookie)
      println(parsedCookie)
    }else{
      println("We did not find a cookie")
    }
    println(
      conn.getRemoteSocketAddress.getAddress.getHostAddress +
        " entered the room!")
  }

  override def onClose(conn: WebSocket,
                       code: Int,
                       reason: String,
                       remote: Boolean): Unit = {
    broadcast(conn + " has left the room!")
    println(conn + " has left the room!")
  }

  override def onMessage(conn: WebSocket, message: String): Unit = {
    broadcast(message)
    println(conn + "we heard: " + message)
  }

  override def onMessage(conn: WebSocket, message: ByteBuffer): Unit = {
    broadcast(message.array())
    println(conn + "we heard: " + message)
  }

  override def onError(conn: WebSocket, ex: Exception): Unit = {
    ex.printStackTrace()
    if (conn != null) {}
  }

  override def onStart(): Unit = {
    println("Server started!")
    setConnectionLostTimeout(0)
    setConnectionLostTimeout(100)
  }

}

Upvotes: 0

Related Questions