Skip to content

Instantly share code, notes, and snippets.

@celaus
Last active August 29, 2015 14:22
Show Gist options
  • Save celaus/5e36511292fcf8074577 to your computer and use it in GitHub Desktop.
Save celaus/5e36511292fcf8074577 to your computer and use it in GitHub Desktop.
JWT-authentication in a proper Authentication HTTP header for Spray (http://spray.io/).
package service.common.config
/**
* Required configuration properties for authentication.
*/
trait AuthenticationConfiguration {
/**
* The secret for signing tokens.
* @return The secret.
*/
def secret: String
/**
* Duration until a token expires.
* @return A duration.
*/
def tokenExpiration: org.joda.time.Duration
/**
* The name of the algorithm used to sign tokens.
* @return The string name.
*/
def algorithmName: String
}
import authentikat.jwt._ // https://github.com/jasongoodwin/authentikat-jwt
import spray.http.{HttpCredentials, HttpRequest, OAuth2BearerToken}
import spray.routing.RequestContext
import spray.routing.authentication.HttpAuthenticator
import com.fasterxml.jackson.core.JsonParseException
import com.typesafe.scalalogging.LazyLogging
import scala.concurrent.{ExecutionContext, Future}
import service.common.config.AuthenticationConfiguration
package service.web.authentication
/**
* Type definition for easier handling.
*/
package object authentication {
type TokenAuthenticator[T] = Option[ Map[String, String]] ⇒ Future[Option[T]]
}
/**
* Implements HTTP-Header-based authentication with a JSON Web Token to use in Spray.
* @param tokenAuthenticator The authenticator method.
* @param realm A resource or domain where access is requested.
* @param authenticationConfig The configuration for JWT (secret, algorithm).
* @param executionContext ... Spray stuff
* @tparam U ... Spray stuff
*/
class HttpJWTAuthenticator[U](val tokenAuthenticator: TokenAuthenticator[U], val realm: String, val authenticationConfig: AuthenticationConfiguration)(implicit val executionContext: ExecutionContext)
extends HttpAuthenticator[U] with LazyLogging {
def getChallengeHeaders(httpRequest: HttpRequest) = List()
override def authenticate(credentials: Option[HttpCredentials], ctx: RequestContext): Future[Option[U]] = try {
tokenAuthenticator {
credentials.getOrElse(None) match {
case t: OAuth2BearerToken => if (JsonWebToken.validate(t.token, authenticationConfig.secret))
t.token match {
case JsonWebToken(header, claims, signature) => Some(claims.asSimpleMap.get)
case _ => None
}
else None
case _ => None
}
}
}
catch {
case e: JsonParseException => Future(None)
}
}
/**
* Object to apply authentication.
*/
object JWTAuthentication {
def apply[U](tokenAuthenticator: TokenAuthenticator[U], realm: String, authenticationConfig: AuthenticationConfiguration)(implicit ec: ExecutionContext): HttpAuthenticator[U] =
new HttpJWTAuthenticator[U](tokenAuthenticator, realm, authenticationConfig)
}
// This method should verify the contents of the JWT claims set and make it a proper object.
def authenticator(claims: Option[Map[String, String]])(implicit ec: ExecutionContext): Future[Option[AuthenticationInfo]] = Future { ... }
// the route. notice how "get" and "authenticate" are nested: CORS traits then can rely on MethodRejection
lazy val route = pathPrefix("a" / "prefix") {
pathEndOrSingleSlash {
get {
authenticate(JWTAuthentication(authenticator, "realm", authenticationConfigurationInstance)) { authInfo =>
// do stuff here
complete("hello world")
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment