I'm testing Json HTTP Routes with Akka Basic Authentication and scala. But the routes don't work as needed. Here is the code
First the secured route is not working with postman BasicAuth. I need to add an email"secured/test#test.com" to trigger the logged in action. Second the subroute basketactor is not working at all. Postman says unknown Route. So how can i make this work with Postman BasicAuth Http Header? What kind of request do i have to send just "secured or with email and password together.
Second how can i make the subroutes basketActor/add work.
~ Route.seal {
(pathPrefix("secured") & get) {
authenticateBasic(realm = "secure site", myUserPassAuthenticator) { email =>
path(Segment) { segment =>
var user = User.USER_LIST.find(_.id == segment)
if (user.isDefined)
complete(s"Hi '$email' you logged in")
else complete(s"login failed Please register at ${"users" / "register"}")
}
} ~ path("basketActor") {
(pathEnd & get) {
implicit val timeout = Timeout(3 seconds)
onSuccess((basketActor ? GetBasketProducts).mapTo[BasketList]) { res =>
complete(BasketContainer(products = res.products))
}
} ~ (path("add" / IntNumber) & post) { number =>
val product = Product.PRODUCT_LIST.find(_.id == number)
if (product.isDefined) {
product.foreach { product =>
basketActor ! AddProductToBasket(product)
// complete(Basket.BASKET_LIST ::= product)
}
complete(product)
} else complete("Not found")
}
}
}
}
def myUserPassAuthenticator(credentials: Credentials): Option[String] =
credentials match {
case p#Credentials.Provided(id) if p.verify("secret") => Some(id)
case _ => None
}
So my expected result should be the user calls the secured Route logs in and then he can access the basketActor route. But now the route is not accessible and the secured route is also not working
This is how Basic Authentication works in Akka HTTP:
The Authorization header in an HTTP Request is used for Basic Authentication, and it contains a username and password encoded using base64 encoding. The authenticateBasic route will extract this information from the header to create a Credentials object, and then pass this to your myUserPassAuthenticator method. This method should check that the username and password match and if it succeeds it should return an object representing that user in your system, or return None on failure.
The second (curried) argument to authenticateBasic is a function that takes the user data and returns a Route. So in your case the email argument will contain the id value returned by myUserPassAuthenticator. This function should return a route that contains all the paths that require authentication.
It is important to realise that there is no concept of being "logged in" with Basic Authentication. There are just authentication credentials that are either correct or incorrect. (If you want to have login/logout semantics you need to move to a token-based authentication mechanism).
So to make your routes work you need to move the logic that looks-up a user ID into myUserPassAuthenticator and return the user object as the result. As it happens you can just return the result of find because this already returns an Option[User].
You also need to make sure that all the authenticated routes are inside the authenticateBasic route. Currently the basketActor and add routes are at the same level and therefore are not authenticated.
A sample route would look like this. I have used concat rather than ~ as I think it is clearer.
pathPrefix("secured") {
authenticateBasic(realm = "secure site", myUserPassAuthenticator) { user =>
concat(
(pathEnd | pathSingleSlash) {
get {
complete(s"User $user Authenticated OK")
}
},
path("basketActor") {
(pathEnd & get) {
complete(s"basketActor for $user")
}
},
path("add" / IntNumber) { number =>
post {
complete(s"User $user posted $number")
}
}
)
}
}
Note that the user ID (email address) is not in the URL, it is in the request in the Basic Authentication header.
To test this with Postman you need to go create a GET with the URL .../secured or .../secured/basketActor, or a POST with the URL .../secured/add/1. Under the Auth tab you need to select Basic Authentication and add the credentials there.
Related
i want to write code which will refresh cookie (via set-cookie http header) in Lagom.
For clarification Cookie is an encoded string (for example AES).
Lets take lagom service call composition for authentication from Implementing services and edit it
def authenticated[Request, Response](
serviceCall: User => ServerServiceCall[Request, Response]
) = ServerServiceCall.composeAsync { requestHeader =>
//Get cookie from header and decode it
val cookie = decodeCookie(requestHeader)
//Get user based on cookie decode function
val userLookup = getUser(cookie)
userLookup.map {
case Some(user) =>
serviceCall(user)
case None => throw Forbidden("User must be authenticated")
}
}
It is possible to manipulate serviceCall(user) response headers?
I tried something like this:
serviceCall( employee ).handleResponseHeader { case (responseHeader, response) =>
responseHeader.withHeader("Set-Cookie",encodeCookie("NewCookieStringExample")) // Add header
response
}
But function handleResponseHeader requires only response[T] as result and header would not be changed because is immutable.
I know that i can pass cookie to serviceCall(user) and in every service call implementation return tuple with ResponseHeader and Response but this will impact all endpoints and will add much more code.
Use of HeaderFilter is possible too but this would decode and encode cookie twice in one request (in header filter and in authenticated service call composition )
Any tips?
You can return the modified Header and the Response as a Tuple:
serviceCall( employee ).handleResponseHeader((responseHeader, response) => {
val modifiedHeader = responseHeader.withHeader("Set-Cookie",encodeCookie("NewCookieStringExample")) // Add header
(modifiedHeader, response)
})
By the way, The provided example does not compile for me. composeAsync wants a Future[ServerServiceCall[...]].
I am using 2 API calls in my Gatling simulation. One API returns the authentication token for the second, so I need to call the token generation API call only once during the execution and use it's generated token for the second API throughout the execution. But this works only for the first cycle of execution and the token that I have saved is not getting used for the remaining executions.
object KeycloakToken extends CMS {
def request(conf: ExecutionConfig): HttpRequestBuilder = {
http("Get Auth token from Keycloak")
.post(s"${conf.authUrl}/token")
.body(StringBody(""" {
"realm": "realm",
"clientId": "clientId",
"clientSecret": "clientSecret",
"grantType": "urn:ietf:params:oauth:grant-type:token-exchange",
"userName" : "userName"
} """)).asJson
.check(jsonPath("$.access_token").saveAs("token"))
}
}
object getOffers extends CMS {
def request(conf: ExecutionConfig): HttpRequestBuilder = {
http("getOffers")
.post(s"$context")
.body(StringBody(""" Body """)).asJson
.headers(headers)
.header("Authorization", s => s"Bearer ${s.attributes("token")}")
.check(jsonPath("$.data.offers.offers[0].id").exists)
}
}
execute.apply(
scenario("service")
.exec(session => session.set("counter" , myGlobalVar.getAndIncrement))
.doIf(session => session("counter").validate[Int].map(i => i == 0)) {
exec(KeycloakToken.request(conf)) //--call only once
}
.exec(getOffers.request(conf))
)
A gatling session object is unique to each user. So when you set your counter session variable as the first step of your scenario then use a doIf to only get the token if counter==0 only the first user to execute will ever try to get the token.
Since the session is unique to that user, none of the other users will have a value for token in their session object.
What you're trying to do seems to be a pretty common issue - make a single request to get some kind of data, then have that data shared among all the users. eg: here
Note that it looks like this kind of scenario will be easier once gatling 3.4
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've written an API based on Play with Scala and I'm quite happy with the results. I'm in a stage where I'm looking at optimising and refactoring the code for the next version of the API and I had a few questions, the most pressing of which is authentication and the way I manage authentication.
The product I've written deals with businesses, so exposing Username + Password with each request, or maintaining sessions on the server side weren't the best options. So here's how authentication works for my application:
User authenticates with username/password.
Server returns a token associated with the user (stored as a column in the user table)
Each request made to the server from that point must contain a token.
Token is changed when a user logs out, and also periodically.
Now, my implementation of this is quite straightforward – I have several forms for all the API endpoints, each one of which expects a token against which it looks up the user and then evaluates if the user is allowed to make the change in the request, or get the data. So each of the forms in the authenticated realm are forms that need a token, and then several other parameters depending on the API endpoint.
What this causes is repetition. Each one of the forms that I'm using has to have a verification part based on the token. And its obviously not the briefest way to go about it. I keep needing to replicate the same kind of code over and over again.
I've been reading up about Play filters and have a few questions:
Is token based authentication using Play filters a good idea?
Can a filter not be applied for a certain request path?
If I look up a user based on the supplied token in a filter, can the looked up user object be passed on to the action so that we don't end up repeating the lookup for that request? (see example of how I'm approaching this situation.)
case class ItemDelete(usrToken: String, id: Long) {
var usr: User = null
var item: Item = null
}
val itemDeleteForm = Form(
mapping(
"token" -> nonEmptyText,
"id" -> longNumber
) (ItemDelete.apply)(ItemDelete.unapply)
verifying("unauthorized",
del => {
del.usr = User.authenticateByToken(del.usrToken)
del.usr match {
case null => false
case _ => true
}
}
)
verifying("no such item",
del => {
if (del.usr == null) false
Item.find.where
.eq("id", del.id)
.eq("companyId", del.usr.companyId) // reusing the 'usr' object, avoiding multiple db lookups
.findList.toList match {
case Nil => false
case List(item, _*) => {
del.item = item
true
}
}
}
)
)
Take a look at Action Composition, it allows you to inspect and transform a request on an action. If you use a Play Filter then it will be run on EVERY request made.
For example you can make a TokenAction which inspects the request and if a token has been found then refine the request to include the information based on the token, for example the user. And if no token has been found then return another result, like Unauthorized, Redirect or Forbidden.
I made a SessionRequest which has a user property with the optionally logged in user, it first looks up an existing session from the database and then takes the attached user and passes it to the request
A filter (WithUser) will then intercept the SessionRequest, if no user is available then redirect the user to the login page
// Request which optionally has a user
class SessionRequest[A](val user: Option[User], request: Request[A]) extends WrappedRequest[A](request)
object SessionAction extends ActionBuilder[SessionRequest] with ActionTransformer[Request, SessionRequest] {
def transform[A](request: Request[A]): Future[SessionRequest[A]] = Future.successful {
val optionalJsonRequest: Option[Request[AnyContent]] = request match {
case r: Request[AnyContent] => Some(r)
case _ => None
}
val result = {
// Check if token is in JSON request
for {
jsonRequest <- optionalJsonRequest
json <- jsonRequest.body.asJson
sessionToken <- (json \ "auth" \ "session").asOpt[String]
session <- SessionRepository.findByToken(sessionToken)
} yield session
} orElse {
// Else check if the token is in a cookie
for {
cookie <- request.cookies.get("sessionid")
sessionToken = cookie.value
session <- SessionRepository.findByToken(sessionToken)
} yield session
} orElse {
// Else check if its added in the header
for {
header <- request.headers.get("sessionid")
session <- SessionRepository.findByToken(header)
} yield session
}
result.map(x => new SessionRequest(x.user, request)).getOrElse(new SessionRequest(None, request))
}
}
// Redirect the request if there is no user attached to the request
object WithUser extends ActionFilter[SessionRequest] {
def filter[A](request: SessionRequest[A]): Future[Option[Result]] = Future.successful {
request.user.map(x => None).getOrElse(Some(Redirect("http://website/loginpage")))
}
}
You can then use it on a action
def index = (SessionAction andThen WithUser) { request =>
val user = request.user
Ok("Hello " + user.name)
}
I hope this will give you an idea on how to use Action Composition
The people at Stormpath has a sample Play application providing authentication via their Backend Service. Some of its code could be useful to you.
It uses username/password rather than tokens, but it should not be complex to modify that.
They have followed this Play Document:
https://www.playframework.com/documentation/2.0.8/ScalaSecurity.
The specific implementation for this is here:
https://github.com/stormpath/stormpath-play-sample/blob/dev/app/controllers/MainController.scala.
This Controller handles authentication operations and provides the isAuthenticated action via the Secured Trait (relying on play.api.mvc.Security). This operation checks if the user is
authenticated and redirects him to the login screen if he is not:
/**
* Action for authenticated users.
*/
def IsAuthenticated(f: => String => Request[AnyContent] => Future[SimpleResult]) =
Security.Authenticated(email, onUnauthorized) { user =>
Action.async { request =>
email(request).map { login =>
f(login)(request)
}.getOrElse(Future.successful(onUnauthorized(request)))
}
}
Then, each controller that needs authenticated operations must use the
Secured Trait:
object MyController extends Controller with Secured
And those operations are "wrapped" with the IsAuthenticated action:
def deleteItem(key: String) = IsAuthenticated { username => implicit request =>
val future = Future {
MyModel.deleteItem(request.session.get("id").get, key)
Ok
}
future.map(
status => status
)
}
Note that the deleteItem operation does not need a username, only the key. However, the authentication information is automatically obtained from the session. So, the business' API does not get polluted with security-specific parameters.
BTW, that application seems to have never been officially released so consider this code a proof of concept.
Spray is hard!! I now know that my knowledge on HTTP protocol is not nearly enough and API design isn't easy. However, I still very much want my practice app to work. I'm writing this authentication for POST/PUT/DELETE method. It appears that there are at least two ways to do this: BasicAuth or write a custom directive.
I found this article:
BasicAuth: https://github.com/jacobus/s4/blob/master/src/main/scala/s4/rest/S4Service.scala
I'm trying it out because it looks simple.
The compile and run stages are fine, and the server runs. However, when I'm trying to send a PUT request to test the implementation (using Python's Httpie: http PUT 127.0.0.1:8080/sec-company/casper username=username token=123), the feedback is:HTTP/1.1 404 Not Found
Here's my route:
pathPrefix("sec-company") {
path("casper") {
//first examine username and token
authenticate(BasicAuth(CustomUserPassAuthenticator, "company-security")) {userProfile =>
post { ctx =>
entity(as[Company.Company]) { company =>
complete {
company
}
}
}
}
Here is my implementation of UserPassAuthenticator:
object CustomUserPassAuthenticator extends UserPassAuthenticator[UserProfile] {
def apply(userPass: Option[UserPass]) = Promise.successful(
userPass match {
case Some(UserPass(user, token)) => getUserProfile(user, token)
case _ => None
}
).future
}
First of all, is this the right way to implement authentication? Second, where does UserPassAuthenticator find the username and password?? Can I send back a better HTTP header other than 404 to indicate failed authentication?
If this is far from correct, is there any tutorial on authentication that I can follow? TypeSafe's Spray templates are more about overall patterns and less about Spray's functionality!
Thank you!
I had the same problem, even after looking at https://github.com/spray/spray/wiki/Authentication-Authorization (which says it's for an older version of Akka but it still seems to apply) I came up with the following:
trait Authenticator {
def basicUserAuthenticator(implicit ec: ExecutionContext): AuthMagnet[AuthInfo] = {
def validateUser(userPass: Option[UserPass]): Option[AuthInfo] = {
for {
p <- userPass
user <- Repository.apiUsers(p.user)
if user.passwordMatches(p.pass)
} yield AuthInfo(user)
}
def authenticator(userPass: Option[UserPass]): Future[Option[AuthInfo]] = Future { validateUser(userPass) }
BasicAuth(authenticator _, realm = "Private API")
}
}
I mix in this trait into the Actor that runs the routes and then I call it like this:
runRoute(
pathPrefix("api") {
authenticate(basicUserAuthenticator) { authInfo =>
path("private") {
get {
authorize(authInfo.hasPermission("get") {
// ... and so on and so forth
}
}
}
}
}
}
The AuthInfo object returned by the validateUser method is passed as a parameter to the closure given to the authorize method. Here it is:
case class AuthInfo(user: ApiUser) {
def hasPermission(permission: String) = user.hasPermission(permission)
}
In Spray (and HTTP), authentication (determining whether you have a valid user) is separate from authorization (determining whether the user has access to a resource). In the ApiUser class I also store the set of permissions the user has. This is a simplified version, my hasPermission method is a bit more complex since I also parametrize permissions, so it's not just that a particular user has permission to do a get on a resource, he might have permission to read only some parts of that resource. You might make things very simple (any logged-in user can access any resource) or extremely complex, depending on your needs.
As to your question, when using HTTP BASIC authentication (the BasicAuth object), the credentials are passed in the request in an Authorization: header. Your HTTP library should take care of generating that for you. According to the HTTP standard, the server should return a 401 status code if the authentication was incorrect or not provided, or a 403 status code if the authentication was correct but the user doesn't have permissions to view the content.