passport.js RESTful auth
Asked Answered
F

3

158

How does one handle authentication (local and Facebook, for example) using passport.js, through a RESTful API instead of through a web interface?

Specific concerns are handling the passing of data from callbacks to a RESTful response (JSON) vs using a typical res.send({ data: req.data }), setting up an initial /login endpoint which redirects to Facebook (/login cannot be accessed via AJAX, because it is not a JSON response - it is a redirect to Facebook with a callback).

I've found https://github.com/halrobertson/test-restify-passport-facebook, but I'm having trouble understanding it.

Furthermore, how does passport.js store the auth credentials? The server (or is it service?) is backed by MongoDB, and I'd expect credentials (login & salted hash of pw) to be stored there, but I don't know if passport.js has this type of capability.

Flavour answered 28/1, 2013 at 22:41 Comment(1)
Since you're new to Node, start easy and check out the example application for passport-facebook. After you get that working, next step is to start understanding how Passport works, and how it stores credentials. Hooking it up to Restify (see here for an updated version of the one you mention) would be one of the last steps (or you could implement the REST interface in Express).Armillas
W
316

There are many questions asked here, and it seems that even though the questions are asked in the context of Node and passport.js the real questions are more about workflow than how to do this with a particular technology.

Let's use @Keith example setup, modified a bit for added security:

  • Web server at https://example.com serves a single page Javascript client app
  • RESTful web service at https://example.com/api provides server support to rich client app
  • Server implemented in Node and passport.js.
  • Server has a database (any kind) with a "users" table.
  • Username/password and Facebook Connect are offered as authentication options
  • Rich client makes REST requests into https://example.com/api
  • There may be other clients (phone apps, for example) that use the web service at https://example.com/api but do not know about the web server at https://example.com.

Note that I'm using secure HTTP. This is in my opinion a must for any service that is available in the open, since sensitive information like passwords and authorization tokens are passing between client and server.

Username/password authentication

Let's look at how plain old authentication works first.

  • The user connects to https://example.com
  • The server serves a rich Javascript application which renders the initial page. Somehwere in the page there is a login form.
  • Many of the sections of this single page app haven't been populated with data due to the user not being logged in. All these sections have an event listener on a "login" event. All this is client side stuff, the server does not know of these events.
  • User enters his/her login and password and hits the submit button, which triggers a Javascript handler to record the username and password in client side variables. Then this handler triggers the "login" event. Again, this is all client side action, credentials were not sent to the server yet.
  • The listeners of the "login" event are invoked. Each of these now needs to send one or more requests to the RESTful API at https://example.com/api to obtain the user specific data to render on the page. Every single request they send to the web service will include the username and password, possibly in the form of HTTP Basic authentication, since the service being RESTful isn't allowed to maintain client state from one request to the next. Since the web service is on secure HTTP the password is safely encrypted during transit.
  • The web service at https://example.com/api receives a bunch of individual requests, each with authentication information. The username and password in each request is checked against the user database and if found correct the requested function executes and data is returned to the client in JSON format. If username and password do not match an error is sent to the client in the form of a 401 HTTP error code.
  • Instead of forcing clients to send username and password with every request you can have a "get_access_token" function in your RESTful service that takes the username and password and responds with a token, which is some sort of cryptographic hash that is unique and has some expiration date associated with it. These tokens are stored in the database with each user. Then the client sends the access token in subsequent requests. The access token will then be validated against the database instead of the username and password.
  • Non browser client applications like phone apps do the same as above, they ask user to enter his/her credentials, then send them (or an access token generated from them) with every request to the web service.

The important take away point from this example is that RESTful web services require authentication with every request.

An additional layer of security in this scenario would add client application authorization in addition to the user authentication. For example, if you have the web client, iOS and Android apps all using the web service you may want the server to know which of the three the client of a given request is, regardless of who the authenticated user is. This can enable your web service to restrict certain functions to specific clients. For this you could use API keys and secrets, see this answer for some ideas on that.

Facebook authentication

The workflow above does not work for Facebook connect because the login via Facebook has a third party, Facebook itself. The login procedure requires the user to be redirected to Facebook's website where credentials are entered outside of our control.

So let's see how things change:.

  • The user connects to https://example.com
  • The server serves a rich Javascript application which renders the initial page. Somehwere in the page there is a login form that includes a "Login with Facebook" button.
  • The user clicks the "Login with Facebook" button, which is just a link that redirects to (for example) https://example.com/auth/facebook.
  • The https://example.com/auth/facebook route is handled by passport.js (see the documentation)
  • All the user sees is that the page changes and now they are in a Facebook hosted page where they need to login and authorize our web application. This is completely outside of our control.
  • The user logs in to Facebook and gives permission to our application, so Facebook now redirects back to the callback URL that we configured in the passport.js setup, which following the example in the documentation is https://example.com/auth/facebook/callback
  • The passport.js handler for the https://example.com/auth/facebook/callback route will invoke the callback function that receives the Facebook access token and some user information from Facebook, including the user's email address.
  • With the email we can locate the user in our database and store the Facebook access token with it.
  • The last thing you do in the Facebook callback is to redirect back to the rich client application, but this time we need to pass the username and the access token to the client so that it can use them. This can be done in a number of ways. For example, Javascript variables can be added to the page through a server-side template engine, or else a cookie can be returned with this information. (thanks to @RyanKimber for pointing out the security issues with passing this data in the URL, as I initially suggested).
  • So now we start the single page app one more time, but the client has the username and the access token.
  • The client application can trigger the "login" event immediately and let the different parts of the application request the information that they need from the web service.
  • All the requests sent to https://example.com/api will include the Facebook access token for authentication, or the application's own access token generated from Facebook's token via a "get_access_token" function in the REST API.
  • The non-browser apps have it a bit more difficult here, because OAuth requires a web browser for logging in. To login from a phone or desktop app you will need to start a browser to do the redirect to Facebook, and even worse, you need a way for the browser to pass the Facebook access token back to the application via some mechanism.

I hope this answers most of the questions. Of course you can replace Facebook with Twitter, Google, or any other OAuth based authentication service.

I'd be interested to know if someone has a simpler way to deal with this.

Waterlog answered 27/5, 2013 at 22:55 Comment(15)
Hey Miguel - this is definitely the more robust answer I was looking for. I conquered this issue by creating my own token system on the REST api side which is based on the access token passed by FB, just as you stated. I think the thing that makes it difficult to grok is that the Facebook auth cannot be RESTful - you have to redirect people, so that request and response needs to be handled by your web app. I definitely think your answer will put people in the right direction :) thanks a tonBeghard
Thank you for your detailed response. Just one question: you say that Every single request they send to the web service will include the username and password, and yet you say you can have a "get_access_token" function in your RESTful service. It seems contradictory to say that REST needs to be stateless, but storing access tokens server side is OK, since that act of storing access tokens means that the server is now stateful. I would appreciate any clarification or justification regarding this. Thanks! :)Flavour
When I think about the stateless requirement I think about state for a particular session with a client. I don't see storing data associated with a user in a database, like a password or an access token as being "state", at least not "session state". Your server obviously needs to know who the users are and their passwords, but this is application data that is not associated with a session. That said, a lot of people use cookies in supposedly RESTful services, so how strict you want to adhere to the REST specification is really up to each implementer.Waterlog
@Miguel, thanks for the great answer! One thing: what do you mean by "User enters his/her login and password and hits the submit button, which triggers a Javascript handler to record the username and password in client side variables". How is that achieved? Any example link?Perorate
@Dexter: In the traditional login case a user enters username and password into a form and when he hits the Submit button this information is posted to a web server. In this case this does not happen, the user fills out the form and when he hits Submit a Javascript handler (an onClick event in the submit button) captures the data and keeps it in the client context. I don't have an example ready to show, but watch out for a second part to this tutorial on my blog where I'll show how it is done: blog.miguelgrinberg.com/post/…Waterlog
@Miguel thanks! I was particularly interested in how to keep data in the client context when my authentication API returns a token after a successful login through AJAX, to use it for the following requests! ;) I'll keep an eye on the blog!Perorate
It is fairly trivial to create a signed HMAC token in Nodejs using the crypto module. This can be passed as an accesstoken to a REST api service and does not require any server side state, only that the signature is checked against the details in the token.Quadratic
This is a very well thought out write up, but contains one important oversight or error. When handling the Facebook login (or Github, twitter, etc), it would be much preferred to pass the token back to the client in a cookie and to then delete the cookie on the client-side once it is discovered. Passing the token as a part of the URL string will add this URL to the browser history and (if things are handled improperly) could lead to this URL being requested by the browser. Either makes the token visible. Anyone who then copied the URL could spoof this user in your application.Perfumer
@RyanKimber: yes, you are correct, that was not a good suggestion. I have made a correction above. I suggested the URL just to simplify things, but that was a mistake. In fact I don't use a URL either, I generate Javascript in the page that sets these values as variables. Thanks.Waterlog
I'm late to the party and I hope you don't mind me asking you directly @Miguel - but would you consider 'Basic' (over HTTPS) authentication sufficient for a 'first party' login system for accessing an API? The more I research passport strategies (node/express) the more confused I get. 'Digest' seems like it opens a can of worms I'd rather avoid. It seems 'Basic' most closely mimicks what I'd call a 'normal' login system for a web app - is this assumption correct? (also, thanks for the fantastic explanation in your answer)Dishonorable
@Nathan: basic auth over https is secure, so yes, that is an acceptable mechanism.Waterlog
@Miguel Thanks Miguel - I figured as much, but always good to have confirmation!Dishonorable
@Miguel, may I ask you to have a look at a passport.js related question here : tinyurl.com/mpro6fv ?Carmelacarmelia
@Miguel You say (in Username/password authentication) that the token is stored in the DB alongside the username and password. Then I was wondering, why is it that I can identify a user with a (JWT) token without any query to the DB, and that it seems secured enough? (for example this article shows the use of JWT and the token is not stored in DB: blog.jscrambler.com/implementing-jwt-using-passport)Odericus
@Odericus There are basically two types of tokens. The tokens I was referring in this answer are basically random strings. To verify these tokens, you have to store them in the database. JWT belongs in a different class of tokens that contain crypto-signed information in them. In a JWT, you can store a username and if the signature in the token is valid, then you can assume that the token is valid and belongs to the username stored in it. This is a nice aspect of JWTs, you can verify them on their own, no need to store them. But on the other side, revoking a JWT requires storing it in a db.Waterlog
S
11

I greatly appreciate @Miguel's explanation with the complete flow in each cases, but I'd like to add some on the Facebook Authentication part.

Facebook provides a Javascript SDK which you can use to get the access token on client-end directly, which is then passed to the server and used to further pull all the user information from Facebook. So you don't need any re-directs basically.

Moreover, you can use the same API end-point for mobile applications as well. Just use the Android / iOS SDK for Facebook, obtain the Facebook access_token on the client end and pass it to the server.

Regarding the stateless nature as explained, when get_access_token is used to generate a token and passed to the client, this token is also stored on the server. So it's as good as a session token and I believe this makes it stateful ?

Just my 2 cents..

Spermogonium answered 6/6, 2013 at 9:28 Comment(5)
This is very important, because this way you can do one-page only-ajax auth using facebook. Token is EQUALLY secure as session auth, the only difference is that a token can be passed to another domain, and the session is used only in one specific domain. When using expressjs and passport, you can make an api that saves a state, and make use of session auth.Kreplach
The javascript api is great if you want to authenticate the user to perform actions against Facebook, but useless by itself if you want to validate the user against your server/database as far as I can tell.Quadratic
You can also use the method described by Miguel above, but then issue your own JWT token as a cookie when redirecting the client in the auth callback. This allows the best of both worlds - your single-page application can be concerned about only one type of authentication (JWT), while keeping the same level of security and providing the flexibility to support any of the social logins without having to make use of specific JavaScript APIs for each social network (Facebook, Twitter, LinkedIn, Google, etc). It also lets you maintain AJAX-style support for username/password and REST access.Perfumer
Facebook Javascript SDK doesn't currently work with Chrome iOS. Maybe an issue for some.Cilice
@RyanKimber can you write a small git repo or similar where this is done as an example im totally stuckDaff
L
3

Here is an awesome article I found that can help you authenticate with:

  • Facebook
  • Twitter
  • Google
  • Local Auth

Easy Node Authentication: Setup and Local

Lek answered 22/4, 2014 at 7:55 Comment(2)
Your link does not lead to an article but instead to a list of articles tagged with 'javascript'Repurchase
Thanks. Updated the link. They changed had it.Lek

© 2022 - 2024 — McMap. All rights reserved.