kikuomax / spray-jwt

JWT related directives for spray.io
MIT License
8 stars 9 forks source link

Secure Cookie Support For Browser Apps #1

Open jnadler opened 9 years ago

jnadler commented 9 years ago

Your spray-jwt project is working very well for me, thank you for open- sourcing this!

We are developing a browser-based application. The browser will need to store the jwt token for use on later requests.

From what I have read, the best way to do this is in a HttpOnly+Secure cookie, so that the token is not available to JavaScript and thus not vulnerable to XSS attack.

Setting the cookie in the authentication response does not require any change to your code. In authorizeToken() it would need to get the value from a cookie instead of from the Authorization header. Because I'll have both mobile clients (which will continue to use the Authorization header) and web clients (secure cookie) it should fall back from one to the other.

Any thoughts about implementing this? One option would be to pass in a 'tokenExtractor' function that moves the work to get the JWT string outside authorizeToken.

If I work on this would you be interested in a PR?

Thanks,

Jeff

kikuomax commented 9 years ago

@jnadler I'm thinking to add a function to the JwtAuthorizationMagnet class, that abstracts how you can get a token from a request. Do you have any update about this issue?

jnadler commented 9 years ago

That would be great. I ended up using spray-jwt as is, and added a new token directive that wraps authorizeToken. It checks for a cookie, if found creates the Authorization header, then delegates to JwtDirectives.authorizeToken. A bit less elegant than your proposal but it works OK.

  /**
   * Check for a token cookie, if found modify the request by creating a new Authorization http header containing
   * the token value from the cookie.  This allows the authorizeToken directive in JwtDirectives to be used without
   * modification.
   */
  private def tokenCookieToHeader: Directive0 = {
    optionalCookie("apitoken").flatMap[HNil] {
      case Some(cookie) =>
        logger.info("Using cookie value " + cookie.content)
        mapRequest(r => r.withHeaders(r.headers :+ RawHeader("Authorization", "Bearer " + cookie.content))) & pass
      case None => pass
    }
  }

  /**
   * Wrap up the tokenCookieToHeader directive above along with the authorizeToken directive to minimize the chance
   * of error (e.g. forgetting tokenCookieToHeader on a service) and make routes less verbose.
   */
  def token[T](magnet: JwtAuthorizationMagnet[T]): Directive1[T] = {
    tokenCookieToHeader.hflatMap { _ =>
      JwtDirectives.authorizeToken(magnet)
    }
  }
kikuomax commented 9 years ago

@jnadler I published the version 0.0.2 to the Maven repository. Now you can specify any directive that extracts a token from an HTTP request to the first argument of JwtDirectives.authorizeToken. I think the following snippet could work for you,

authorizeToken(extractJwsFromCookie("apitoken"), someFunction) { x =>
  ...
}
jnadler commented 9 years ago

Thank you for adding this! I will test it soon and let you know how it goes.