Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What authentication strategy to use? [closed]

Recently I have been reading up on OAuth2, OpenID Connect etc. But still very lost at what to use when and how to implement it. I am thinking of using NodeJS for now.

Lets say I want to create a blog service. This service will expose API's for clients to use. "Clients" include an admin CMS. I am thinking it will be nice to decouple my server and client (UI). I can change the UI without touching the server. These clients are likely going to be single page web applications.

Ok 1st question: In this example, should I be using OAuth2? Why? Isit just because I am authorizing the admin app to access by blog?

Since its SPA's, I think the right strategy is OAuth2 Implicit Flow?

For each app, eg. admin cms, I will have to generate an AppID which is passed to the auth server. No app secret is required correct?

Isit possible to use google login in this case (instead of username/password)? Does OpenID connect do this?

How do I implement all these in NodeJS? I see https://github.com/jaredhanson/oauth2orize, but I do not see how to implement the implicit flow.

I do see an unofficial example https://github.com/reneweb/oauth2orize_implicit_example/blob/master/app.js, but what I am thinking is why is sessions required? I thought one of the goals of tokens is so that server can be stateless?

I am also wondering, when should I use API key/secret authentication?

like image 561
Jiew Meng Avatar asked Mar 26 '16 04:03

Jiew Meng


2 Answers

Let's examine your questions

  1. Should I be using OAuth2? Why?

A: Well, as today the old OpenId 2 authentication protocol has been marked as obsolete (November 2014) and OpenId Connect is an identity layer built on top of OAuth2 so the real question is if is important for you and your business to know and verify the identity of your users (the authentication part). If the answer is "yes" then go for OpenId Connect otherwise you can choose any of the two, the one you feel more comfortable with.

  1. Since its SPA's, I think the right strategy is OAuth2 Implicit Flow?

A: Not really. You can implement any strategy when using a SPA, some takes more work than others and greatly depends on what are you trying to accomplish. The implicit flow is the simplest but it does not authenticate your users since an access token is issued directly.

When issuing an access token during the implicit grant flow, the authorization server does not authenticate the client. In some cases, the client identity can be verified via the redirection URI used to deliver the access token to the client.

I would not recommend this flow for your app (or any app that needs a decent level of security1).

If you want to keep it simple you should use Resource Owner Grant flow with an username and password but again there is nothing that prevents you of implementing the Authorization Code Grant flow especially if you want to allow third parties apps to use your service (which in my opinion is a winning strategy) and it will be relatively more secure than the others since it requires explicit consent from the user.

  1. For each app, eg. admin cms, I will have to generate an AppID which is passed to the auth server. No app secret is required correct?

A: Yes that is correct but the client_secret can be used to add an extra layer of security to the token endpoint in the resource owner flow when you can't use Basic authentication, this is not required in any other flow.2 3

The authorization server MUST:

  • require client authentication for confidential clients or for any client that was issued client credentials (or with other authentication requirements),

  • authenticate the client if client authentication is included, and

  • validate the resource owner password credentials using its existing password validation algorithm.

and

Alternatively, the authorization server MAY support including the client credentials in the request-body (...) Including the client credentials in the request-body using the two parameters is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize the HTTP Basic authentication scheme (or other password-based HTTP authentication schemes)

  1. Is it possible to use google login in this case (instead of username/password)? Does OpenID connect do this?

A: Yes, is possible to use google login in which case you are just delegating the authentication and authorization job to the google servers. One of the benefits of working with an authorization server is the ability to have a single login to access other resources without having to create a local account for each of the resources you want to access.

  1. How do I implement all these in NodeJS?

Well you started with the right foot. Using oaut2horize is the most simple way to implement an authorization server to issue tokens. All other libraries I tested were too complicated of use and integrate with node and express (disclaimer: this is just my opinion). OAuthorize plays nicely with passport.js(both from the same author) which is a great framework to enforce the authentication and authorization with over 300+ strategies like google, facebook, github, etc. You can easily integrate google using passport-google(obsolete), passport-google-oauth and passport-google-plus.

Let's go for the example

storage.js

    // An array to store our clients. You should likely store this in a
    // in-memory storage mechanism like Redis
    // you should generate one of this for any of your api consumers
    var clients = [
        {id: 'as34sHWs34'}
        // can include additional info like:
        // client_secret or password
        // redirect uri from which client calls are expected to originate
    ];
    // An array to store our tokens. Like the clients this should go in a memory storage
    var tokens = [];

    // Authorization codes storage. Those will be exchanged for tokens at the end of the flow. 
    // Should be persisted in memory as well for fast access.
    var codes = [];

    module.exports = {
        clients: clients,
        tokens: tokens,
        codes: codes
    };

oauth.js

    // Sample implementation of Authorization Code Grant

    var oauth2orize = require('oauth2orize');
    var _ = require('lodash');
    var storage = require('./storage');

    // Create an authorization server
    var server = oauth2orize.createServer();

    // multiple http request responses will be used in the authorization process 
    // so we need to store the client_id in the session 
    // to later restore it from storage using only the id
    server.serializeClient(function (client, done) {
        // return no error so the flow can continue and pass the client_id.
        return done(null, client.id);
    });

    // here we restore from storage the client serialized in the session 
    // to continue negotiation
    server.deserializeClient(function (id, done) {
        // return no error and pass a full client from the serialized client_id
        return done(null, _.find(clients, {id: id}));
    });

    // this is the logic that will handle step A of oauth 2 flow
    // this function will be invoked when the client try to access the authorization endpoint
    server.grant(oauth2orize.grant.code(function (client, redirectURI, user, ares, done) {
        // you should generate this code any way you want but following the spec
        // https://www.rfc-editor.org/rfc/rfc6749#appendix-A.11
        var generatedGrantCode = uid(16);
        // this is the data we store in memory to use in comparisons later in the flow
        var authCode = {code: generatedGrantCode, client_id: client.id, uri: redirectURI, user_id: user.id};

        // store the code in memory for later retrieval
        codes.push(authCode);

        // and invoke the callback with the code to send it to the client
        // this is where step B of the oauth2 flow takes place.
        // to deny access invoke an error with done(error);
        // to grant access invoke with done(null, code);
        done(null, generatedGrantCode);
    }));

    // Step C is initiated by the user-agent(eg. the browser)

    // This is step D and E of the oauth2 flow
    // where we exchange a code for a token
    server.exchange(oauth2orize.exchange.code(function (client, code, redirectURI, done) {
        var authCode = _.find(codes, {code: code});
        // if the code presented is not found return an error or false to deny access
        if (!authCode) {
            return done(false);
        }
        // if the client_id from the current request is not the same that the previous to obtain the code
        // return false to deny access
        if (client.id !== authCode.client_id) {
            return done(null, false);
        }
        // if the uris from step C and E are not the same deny access
        if (redirectURI !== authCode.uri) {
            return done(null, false);
        }

        // generate a new token
        var generatedTokenCode = uid(256);
        var token = {token: generatedTokenCode, user_id: authCode.user_id, client_id: authCode.client_id};

        tokens.push(token);
        // end the flow in the server by returning a token to the client
        done(null, token);
    }));

    // Sample utility function to generate tokens and grant codes. 
    // Taken from oauth2orize samples
    function uid(len) {
        function getRandomInt(min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        }

        var buf = []
            , chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
            , charlen = chars.length;

        for (var i = 0; i < len; ++i) {
            buf.push(chars[getRandomInt(0, charlen - 1)]);
        }

        return buf.join('');
    }

    module.exports = server;

app.js

    var express = require('express');
    var passport = require('passport');
    var AuthorizationError = require('oauth2orize').AuthorizationError;
    var login = require('connect-ensure-login');
    var storage = require('./storage');
    var _ = require('lodash');

    app = express();

    var server = require('./oauthserver');

    // ... all the standard express configuration
    app.use(express.session({ secret: 'secret code' }));
    app.use(passport.initialize());
    app.use(passport.session());

    app.get('/oauth/authorize',
        login.ensureLoggedIn(),
        server.authorization(function(clientID, redirectURI, done) {
            var client = _.find(storage.clients, {id: clientID});
            if (client) {
                return done(null, client, redirectURI);
            } else {
                return done(new AuthorizationError('Access denied'));
            }
        }),
        function(req, res){
             res.render('dialog', { transactionID: req.oauth2.transactionID, user: req.user, client: req.oauth2.client });
        });

    app.post('/oauth/authorize/decision',
        login.ensureLoggedIn(),
        server.decision()
    );

    app.post('/oauth/token',
        passport.authenticate(['basic', 'oauth2-client-password'], { session: false }),
        server.token(),
        server.errorHandler()
    );
  1. (...) but what I am thinking is why is sessions required? I thought one of the goals of tokens is so that server can be stateless?

When a client redirects a user to user authorization endpoint, an authorization transaction is initiated. To complete the transaction, the user must authenticate and approve the authorization request. Because this may involve multiple HTTP request/response exchanges, the transaction is stored in the session.

Well yes, but the session is used for the token negotiation process. Later you enforce authorization sending the token in an Authorization header to authorize each request using the obtained token.

like image 134
devconcept Avatar answered Oct 02 '22 10:10

devconcept


In my experience, OAuth2 is the standard way of securing APIs. I'd recommend using OpenID Connect though as it adds authentication to OAuth2's otherwise authorization-based spec. You can also get Single-Sign-On between your "clients".

Since its SPA's, I think the right strategy is OAuth2 Implicit Flow?

De-coupling your clients and servers is a nice concept (and I'd generally do the same too) however, I'd recommend the authorization code flow instead as it doesn't expose the token to the browser. Read http://alexbilbie.com/2014/11/oauth-and-javascript/. Use a thin server-side proxy instead to add the tokens to the request. Still, I generally avoid using any server-generated code on the client (like JSPs in java or erb/haml in rails) since it couples the client to the server too much.

For each app, eg. admin cms, I will have to generate an AppID which is passed to the auth server. No app secret is required correct?

You'll need a client ID for implicit flow. If you use authorization code flow (recommended), you'll need both an ID and secret but the secret will be kept in the thin server-side proxy rather than a client-side only app (since it can't be secret in that case)

Is it possible to use google login in this case (instead of username/password)? Does OpenID connect do this?

Yes. Google uses openid connect

How do I implement all these in NodeJS? I see https://github.com/jaredhanson/oauth2orize, but I do not see how to implement the implicit flow.

A nice thing about openid connect is that (if you use another provider like google), you don't have to implement the provider yourself and you'll only need to write client code (and/or utilize client libaries). See http://openid.net/developers/libraries/ for different certified implementations. See https://www.npmjs.com/package/passport-openidconnect for nodejs.

like image 37
sdoxsee Avatar answered Oct 02 '22 10:10

sdoxsee