DEV Community

Using JWTs for Authentication in RESTful Applications

Perry Donham on December 29, 2017

The problem Applications built using the MEAN stack typically use Node, MongoDB and Express on the back end to implement business logic fronted ...
Collapse
 
orkon profile image
Alex Rudenko

Thanks for the great article! One remark: I would not put the actual twitter token inside the JWT. If the interaction is over plain HTTP, the token can be easily intercepted. I would suggest adding the Secure flag to the cookie. tools.ietf.org/html/rfc6265#sectio... The best would be to store only the user/session id in the JWT because it is unpredictable where the data leaks eventually.

Also, in my opinion, the expiration for JWTs is a must. Otherwise, the token gives a forever access to the service. I would highlight that.

Collapse
 
perrydbucs profile image
Perry Donham

Excellent point, Alex! I'll update my lecture and code on this as it clearly would be a potential leak. What do you think about using a constantly changing identifier on the session as I mentioned in the reply above?

Collapse
 
orkon profile image
Alex Rudenko

I think that would work for the session/client-auth use case. I mostly use JWTs in a distributed system where a token is an authorization to access a specific service (also backend-to-backend). For this use case, I always need the user ID in the token. Constantly changing identifier would be problematic to use because the service does not have access to the session or user data usually.

Collapse
 
convene143 profile image
Conven • Edited

Hi Perry , It's very useful article.
need help to set httpOnly flag to an existing response cookie.
Problem is jwt value is visible to client on browser to restrict we need to add security flag to cookies I couldn't find the right cookie Could you please help out me ,,
Here is the code ,

  1. First API call it returns JWT token[jwtToken]

def consumeAuthToken(authCode: String)(implicit req: Request, w: Wiring): Future[Response] = {
for {
withCode <- Try(json"""{ "code": ${authCode}}""").toFuture
r <- w.postgrest ? (method = Post, path = "/rpc/consume_auth_codes", json = Option(withCode))
json <- r.jsonArrHeadF
resp <- json.consume_auth_codes match {
case json""" { "found": false }""" =>
CANNED(NotFound, s"${authCode} is not a valid code", false)
case json""" { "found": true, "used": true }""" =>
CANNED(NotFound, s"${authCode} is not a valid code", false)
case json""" { "found": true, "used": false, "result": { "email": $email , "principal_id": $principal_id } }""" =>
val token = createJWT(email.as[String], principal_id.as[String]).get
CANNED(Ok, json"""{ "jwtToken": $token }""")
case any =>
CANNED(InternalServerError, any)
}
} yield resp
}
Response : jwtToken : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkc3QiOiJjb252ZW5lLXFhIiwiZXhwIjoxNTU5OTAyMTU3LCJpYXQiOjE1NTcyMjM3NTcsImlzcyI6ImNvbnZlbmUtcWEiLCJzdWJfZW1haWwiOiJjb252ZW50ZXN0MDk5QGdtYWlsLmNvbSIsInN1YiI6IjMzYTJlZTNkLWQwMGItNGY4YS1hN2NlLTEwYzI0ZTdiNWJjMiIsInN1Yl9uYW1lIjoiIiwic3ViX2dyYXZhdGFyIjoiIiwic3ViX3Byb3ZpZGVyLW5hbWUiOiJjb252ZW5lLWVtYWlsIiwic3ViX3Byb3ZpZGVyLWlkIjoiY29udmVudGVzdDA5OUBnbWFpbC5jb20ifQ.XAEWEhNL92yYjClNsOgjb1tjIgxyBzhwubhaM5iVrwU'

  1. Second call goes to : -

def ensureUserExists(manager:Boolean)(implicit req: Request, w:Wiring): Future[Response] = {
for {
i <- req.parseJWT.toFuture
pr = i.sub_provider-name.map(_.toString).getOrElse("")
resJF <- checkSignOut()
respS=resJF.getContentString()
_ <- if(pr.equals("convene-email") || manager ) if(respS.size>2 && respS.substring(9,13).equals("true")) Future.Done else Future.exception(HTTPError(BadRequest, " new Invalid request body"))
else Future.Done
i <- req.parseJWT.toFuture
pr_provider = i.sub_provider-name.map(pp => json"""{"pr_provider-name": $pp}""").getOrElse(json"{}")
response <- w.models ? (path = "/api/models/users", method = Post,
json = Some(
json"""{
"pr_id": ${i.sub},
"pr_name": ${i.sub_name},
"pr_email": ${i.sub_email},
"manager": $manager
}""" ++ pr_provider))
resp <- response.jsonF
_ <- if (!manager) checkForPendingInvitations(i.sub) else Future.Done
responseCookies <- if (manager) checkManagerInvitation(i) else Future.value(Seq.empty)
_ <- if (resp.created.as[Boolean]) sendWelcomeEmail(i, manager) else Future.Done
} yield mkResp(Ok, Json.format(resp), cookies = responseCookies)
}

///

def checkManagerInvitation(i: TokenInfo)(implicit req: Request, w: Wiring): Future[Seq[Cookie]] = {
req.cookies.get("convene-manager-login") match {
case None => Future.value(Seq.empty)
case Some(c) =>
println(debug"""Login cookie ${c.value}""")
val payload = Some(json"""{
"principalID": ${i.sub},
"principalEmail": ${i.sub_email},
"principalName": ${i.sub_name},
"principalGravatar": ${i.sub_gravatar},
"blob": ${c.value}
}""")
val resp = w.invitations ? (method = Post, path = "/api/invitations/members/redeem", json = payload, propagateFailedResponse = true)
c.maxAge = 0.seconds
c.httpOnly=true // Is this right position way to enable
resp.map(_ => Seq(c))
}
}

How the jwt is been constructed as part of response .where have to add httpOnly true
@perrydbucs please have a look

Collapse
 
dimitarnestorov profile image
Dimitar Nestorov

Collapse
 
perrydbucs profile image
Perry Donham

The httpOnly flag on the cookie is what prevents the client from being able to see it; no reason not to send other cookies on the response that are visible, or an object that contains the info. I tend to treat the front end as just view, so there wouldn't be any front-end permissions checking -- the front end would only receive data appropriate for the role of the authenticated user.

Same with CSRF, from the front end's viewpoint it is only talking the the app on the back end, so there are no CSRF issues.

This reminds me of a demo that I sometimes do where I show how many websites, especially media (newspapers, etc), do a subscriber check on the front end in Javascript. Flipping the isSubscriber variable is trivial (or just turning JS off) to read the 'subscriber only' content.

Can't trust the client.

Collapse
 
deepaprasanna profile image
Deepa Prasanna

Thanks for the detailed article perry! I never knew until now that most of the websites do a subscription check on frontend. I tried turning off the js and it stopped showing the subscriber check.

Collapse
 
imthedeveloper profile image
ImTheDeveloper

Quite timely I have recently asked a question in relation to this here: dev.to/imthedeveloper/critique-my-...

Something that interests me is the storage of JWT. Whilst I appreciate the usage of cookies can aid to remove the accessibility of the cookie via client side javascript, my implementation required some of the data to be accessible. I went with storing the JWT in local storage. This brought me on to thinking deeper around JWT security. Technically even with a secure cookie I can still use chrome extensions to read it's content and thus extract the JWT. Once I have this JWT there is nothing stopping me passing this token to someone else who would have the exact same privileges as myself.

I assume under scenarios where tokens get passed around or an attacker engineers a method to retrieve such token then there really isn't much else you can do? Maybe browser fingerprinting would help as an additional check?

Collapse
 
perrydbucs profile image
Perry Donham

A really good point; maybe one of our security wonks will drop in and offer an opinion.

One approach might be to use the JWT in combination with a session identifier which changes on each request/response pair. The client would need to present both the signed JWT and the correct session identifier; both would be sent on the request automatically and both would be deleted when the browser or tab is closed. On the server side, seeing the same session identifier twice would be an error and would indicate a possible attack.

Also, requiring HTTPS would reduce the chance of a man-in-the-middle or sniffing attack.

In your case, where some data needs to be visible on the client, I think I'd still use the secure JWT and then send a second object back on the response with the data.

Collapse
 
nitingaur6817 profile image
nitingaur6817

Thx for a. great article. I had a quick question on enabling callbacks with JWT/Serverless... since we do not maintain sessions and in some cases we need to implement a callback to client either for redirect or conversational state and not for a web apps but say API services. -- is there anyway to achieve that? or do I need to implement session state?

Collapse
 
jjjjcccjjf profile image
endan • Edited

Hi Perry! Great article!

I'm curious as how would you implement this alongside with local-login and as well as other 3rd Party login such as facebook and google.

Collapse
 
perrydbucs profile image
Perry Donham

Thank you! The technique would be the same for multiple login paths. Once the user is logged in via a local user/password or a third party, generate the JWT. It's independent of the login method used.

If you are using Passport, its serialize / deserialize methods are a good spot to put JWT processing. As Alex pointed out above, sending the actual access token from a third party back and forth is not a secure method, and so you would want to store some identifying data in the JWT such as the user name or other unique value.

The JWT generation in the example code is done inside a Twitter OAuth callback; in your case with multiple providers you'd want to move that out into its own function that each callback (and your local user / password method) would invoke.

Your question has me thinking again about what responsibilities the JWT checking code should have...I'm starting to lean toward that function placing a user object with relevant user info (fetched from the database) onto the request along with an 'isAuthorized' boolean so that each route could decide what to do next.