Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Added mechanism for self-authenticated clients #21

Closed
wants to merge 1 commit into from

3 participants

@thanpolas

Some providers (like Facebook) offer javascript SDKs that allow for a complete client side authentication. In those cases, the client performs all the authentication steps without the server ever getting touched.

For such cases the verifyAuth mechanism is provided. After the client-side authentication completes, and the Access Token is available, we can perform a POST request to the server with the Access Token in order to:

  1. Inform the server that we are authenticated with a provider
  2. Have the server validate with the provider the authentication
  3. Get back a proper response from the server with our auth logic (our local user data object)

By default the verifyAuth mechanism is turned off.

@jed
Owner

hmmm, i'm not convinced this is worth it.

this library is for aggregating server authentication, from which a client auth workflow is significantly different. i'm a big fan of one thing done right, and am not sure the marginal functionality added outweighs the more complicated API with additional endpoint and slightly awkward parameters (like verifyAuthAccessTokenParam).

wouldn't it be better to have this in a separate library?

@thanpolas

Never mind the style and param naming,

How do you respond to this reality? There is only one way for the server to verify / validate a client side authentication and that is to perform an API call to the provider with the access token the client provides. So one would expect / needs:

  1. His auth library to handle, well, authentication
  2. His auth library to be aware of the auth state (keep the access token)
  3. His auth flow to work as expected ('auth' event trigger)

Have a second library wrapping authom?

@thanpolas

@jed verdict?

@jed
Owner
jed commented

i don't deny the reality, but i just don't see the win in complicating the library... are there really apps that do both client- AND server-side authentication?

if there's a piece of this library that should be factored out to make other scenarios possible, i think it makes more sense to explore that.

@thanpolas

uhmmm i am confused as to where this discussion is heading, a plain No would suffice...

If the core question is:

are there really apps that do both client- AND server-side authentication?

The answer to that is: YES there are. The flow is provided by facebook, LinkedIn, Google+, to name a few, so it's pretty much out there.

If the implementation and the new API commands are what bothers you, then by all means do a code review and point out how you want things done. I'd be more than happy to provide followup commits.

If however you believe that an auth library should not include this flow because it will complicate the library, then i'd implore you to give a fresh, new look, as you are not doing it justice.

@jed
Owner
jed commented

the reason i haven't said no is because i am open to being convinced. this has yet to happen.

i know the flow you describe is provided, i just don't know of any apps that use client-side AND server-side authentication at the same time. it still seems better to build your client-side lib and use authom as a dependency.

@deedubs

I'm going to have to agree that it doesn't seem worth the complication

@jed
Owner

agreed. closing.

@jed jed closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 189 additions and 19 deletions.
  1. +42 −2 README.md
  2. +47 −1 lib/authom.js
  3. +100 −16 lib/services/oauth2.js
View
44 README.md
@@ -158,7 +158,7 @@ github.on("auth", function(req, res, gitHubSpecificData){})
github.on("error", function(req, res, gitHubSpecificData){})
```
-Or, use this to listen to events from all provders, since authom already listens and namespaces them for you:
+Or, use this to listen to events from all providers, since authom already listens and namespaces them for you:
```javascript
authom.on("auth", function(req, res, data){})
@@ -229,12 +229,52 @@ server.listen(8000)
### authom.route
-A regular expression that is run on the pathname of every request. authom will only run if this expression is matched. By default, it is `/^\/auth\/([^\/]+)\/?$/`.
+A regular expression that is run on the pathname of every request. authom will only run if this expression is matched. By default, it is `/^\/auth\/([^\/][\w]+)\/?([\w]+)?.*?$/;`. It matches the second and third part of the path which are required to be matched for authom to function properly:
+
+* [0] The default match, the whole string if there's a match
+* [1] The second part of the path. This should contain the service used (e.g. 'facebook')
+* [2] The third part of the path. This should optionally contain the `verifyAuth` path (see [verifyAuth feature](#client-side-authentication-using-jssdk))
### authom.app
This is a convenience Express app, which should be mounted at a path containing a `:service` parameter.
+## Client side authentication using JS/SDK
+
+Some providers (like Facebook) offer javascript SDKs that allow for a complete client side authentication. In those cases, the client performs all the authentication steps without the server ever getting touched.
+
+For such cases the `verifyAuth` mechanism is provided. After the client-side authentication completes, and the *Access Token* is available, we can perform a POST request to the server with the *Access Token* in order to:
+
+ 1. Inform the server that we are authenticated with a provider
+ 2. Have the server validate with the provider the authentication
+ 3. Get back a proper response from the server with our auth logic (our local user data object)
+
+To enable the `verifyAuth` feature and configure it, the following API properties are available:
+
+### authom.verifyAuth
+
+A boolean parameter, which by default is set to `false`. Set it to `true` to enable the `verifyAuth` feature.
+
+### authom.verifyAuthPath
+
+A string indicating the path where the POST request should point to. By default it is set to `verifyAuth`. So if the provider is Facebook the full path would be:
+
+```
+/auth/facebook/verifyAuth
+```
+
+### authom.verifyAuthAccessTokenParam
+
+A string declaring the name of the *Access Token* parameter as passed from the POST request. By default it is named `accessToken`.
+
+### Additional setup for verifyAuth
+
+If you want to enable `verifyAuth` and you are using express, you need to add one additional line to your `app.js` file:
+
+```javascript
+app.post("/auth/:service/" + authom.verifyAuthPath, authom.app);
+```
+
Providers
---------
View
48 lib/authom.js
@@ -6,7 +6,53 @@ var fs = require("fs")
, authom = module.exports = new EventEmitter
authom.servers = {}
-authom.route = /^\/auth\/([^\/]+)\/?$/
+
+/**
+ * In case the external auth source supports client
+ * side authentication via a JS SDK/API (like Facebook),
+ * it is possible for the client to perform the authentication
+ * process all on its own.
+ *
+ * When that is done, all we have to do, server side, is get the
+ * Access Token and verify that this user may indeed authenticate
+ * with our services honoring his authentication with the third party
+ *
+ * This switch enables or disables this feature.
+ * By default it is disabled
+ *
+ * @type {boolean}
+ */
+authom.verifyAuth = false;
+/**
+ * The string (path) where we can perform an auth verification.
+ *
+ * This string will match on the third part of the path:
+ * e.g. /auth/facebook/verifyAuth
+ * or /auth/linkedin/verifyAuth
+ *
+ * The request to verifyAuth is performed via POST and
+ * an access token is expected to be passed
+ *
+ * @type {string}
+ */
+authom.verifyAuthPath = 'verifyAuth';
+/**
+ * In case of a verifyAuth operation, we expect the access token
+ * to be passed in the POST request that will be made. This variable
+ * defines the name of the parameter where we'll find the access token
+ *
+ * @type {string}
+ */
+authom.verifyAuthAccessTokenParam = 'accessToken';
+/**
+ * This regex will extract the second and third part
+ * of a path if it exists:
+ * [0] Whole sting, if there is a match
+ * [1] Second part of the path
+ * [2] Third part of the path
+ * @type {RegExp}
+ */
+authom.route = /^\/auth\/([^\/][\w]+)\/?([\w]+)?.*?$/
authom.createServer = function(options, listener) {
var service = options.service
View
116 lib/services/oauth2.js
@@ -1,6 +1,7 @@
var url = require("url")
, https = require("https")
, EventEmitter = require("events").EventEmitter
+ , authom = require('authom')
function OAuth2(){}
@@ -52,7 +53,16 @@ OAuth2.prototype.request = function(request, cb) {
OAuth2.prototype.onRequest = function(req, res) {
var uri = req.url = this.parseURI(req)
- if (uri.query.error) this.emit("error", req, res, uri.query)
+ // check for verify auth feature, and see if we have a path match
+ if(authom.verifyAuth) {
+ var match = uri.path.match(authom.route);
+ if (match && authom.verifyAuthPath === match[2]) {
+ this.onVerifyAuth(req, res, uri);
+ return;
+ }
+ }
+
+ if (uri.query.error) this.emit("error", req, res, uri.query);
else if (uri.query.code) this.onCode(req, res)
@@ -77,23 +87,97 @@ OAuth2.prototype.onCode = function(req, res) {
this.request(this.token, function(err, data) {
if (err) return this.emit("error", req, res, err)
- var tokenKey = this.user.tokenKey || "access_token"
-
- this.user.query[tokenKey] = data.access_token
-
- this.request(this.user, function(err, user) {
- if (err) return this.emit("error", req, res, err)
+ // get the user data object from the auth source
+ // and emit auth or error events
+ this.getUserAndAuth(req, res, data.access_token);
+
+ }.bind(this));
+};
+
+/**
+ * Fetch the user data object from the authentication source
+ * and if this operation was successful emit the authentication
+ * event.
+ *
+ * We may optionally set a callback function.
+ *
+ * @param {object} req The request object
+ * @param {object} res The response object
+ * @param {string} accessToken The access token to perform the operation
+ * @param {Function(string?, object)=} opt_cb Optional callback
+ * with the 'err' string as first param
+ * and the 'user' object as second,
+ * the callback is called before we
+ * emit any events ('error' or 'auth').
+ * @return {void} nothing.
+ */
+OAuth2.prototype.getUserAndAuth = function(req, res, accessToken, opt_cb)
+{
+ var cb = opt_cb || function(){};
+
+ var tokenKey = this.user.tokenKey || "access_token";
+
+ this.user.query[tokenKey] = accessToken;
+ this.request(this.user, function(err, user) {
+ if (err) {
+ cb(err).bind(this);
+ return this.emit("error", req, res, err);
+ }
+
+ var data = {
+ token: accessToken,
+ id: this.getId(user),
+ data: user
+ };
+
+ cb(err, user);
+ this.emit("auth", req, res, data);
+ }.bind(this));
+};
+
+
+/**
+ * Performs authentication verification with the external source.
+ *
+ * The authentication has already happened on the client side,
+ * we are just validating that this stands to truth by requesting
+ * the user object from the auth service using the Access Token
+ * provided by the client in this request.
+ *
+ * If we successfully get the auth token then the user is indeed
+ * authenticated and we emit the auth event.
+ *
+ * For security reasons we only allow this operation to be
+ * perfoed by a POST request
+ *
+ * @param {object} req The request object
+ * @param {object} res The response object
+ * @param {object=} opt_uri Optionaly pass the uri object if already
+ * parsed, if not passed we parse again...
+ * @return {void} nothing.
+ */
+OAuth2.prototype.onVerifyAuth = function(req, res, opt_uri)
+{
+ var uri = opt_uri || this.parseURI(req);
+
+ // check if method is POST
+ if ('POST' !== req.method) {
+ this.emit('error', req, res, {message: 'Only POST is allowed for this action'});
+ return;
+ }
+ // get and validate the access token
+ var accessToken = req.param(authom.verifyAuthAccessTokenParam);
+ if ( 'string' === typeof accessToken && 0 < accessToken.length) {
+ // get the user data object from the auth source
+ // and emit auth or error events
+ this.getUserAndAuth(req, res, accessToken);
+ return;
+ }
- data = {
- token: data.access_token,
- id: this.getId(user),
- data: user
- }
+ // not a valid access token
+ this.emit('error', req, res, {message: 'The access token passed was not valid or was not set. Expected param name:' + authom.verifyAuthAccessTokenParam});
- this.emit("auth", req, res, data)
- }.bind(this))
- }.bind(this))
-}
+};
OAuth2.prototype.code = null
OAuth2.prototype.token = null
Something went wrong with that request. Please try again.