Let's look at your questions.
Should I use OAuth2? Why?
A: Well, since the old OpenId 2 authentication protocol was marked obsolete today (November 2014) and OpenId Connect is an authentication layer built on top of OAuth2, so the real question is whether it is important for you and your business to know and verify the identity of your users (part of authentication). If the answer is yes, then go to OpenId Connect, otherwise you can choose either of the two, the one with which you will be comfortable.
Since my SPA, I think the right strategy is OAuth2 Implicit Flow?
A: Not really. You can implement any strategy when using SPA, some of them take more work than others, and are very dependent on what you are trying to accomplish. An implicit stream is the simplest, but it does not authenticate your users , since the access token is issued directly.
When issuing an access token during an implicit grant flow, the authorization server does not authenticate the client. In some cases, the client identifier can be verified using the redirect URI used to deliver the access token to the client.
I would not recommend this thread for your application (or any application that needs a decent level of security 1 ).
If you want to keep it simple, you should use the Resource Owner Grant with a username and password, but again there is nothing that would prevent you from implementing the Authorization code , especially if you want to allow third-party applications to use your service (which, in my opinion, is a winning strategy), and it will be relatively more secure than others, as this requires explicit user consent.
For each application, for example. admin cms, I will have to generate the AppID, which is passed to the auth server. No application secret required?
A: Yes, that’s correct, but client_secret can be used to add an additional level of security to the endpoint of the token in the resource owner’s stream, if you cannot use basic authentication, this is not required in any other stream. 2 3
The authorization server MUST:
client authentication is required for confidential clients or for any client to whom client credentials have been issued (or with other authentication requirements),
authenticate the client if client authentication is enabled, and
Verify the password credentials of the resource owner using its existing password verification algorithm.
and
Alternatively, the authorization server MAY support, including client credentials in the request body (...) Including client credentials in the request body using two parameters is NOT RECOMMENDED and MUST be limited to clients that cannot directly use the basic HTTP authentication scheme (or other password-based HTTP authentication schemes)
Is it possible to use google login in this case (instead of username / password)? Does OpenID include a connection?
A: Yes, you can use the google login, in which case you simply delegate the authentication and authorization task to google servers. One of the advantages of working with an authorization server is the ability to have one login to access other resources without having to create a local account for each of the resources that you want to access.
How to implement all this in NodeJS?
Well, you started with the right foot. Using oaut2horize is the easiest way to implement an authorization server to issue tokens. All other libraries that I tested were too complicated to use and integrate with node and express (disclaimer: this is just my opinion). OAuthorize plays great with passport.js (like the same author), which is an excellent basis for providing authentication and authorization with more than 300+ strategies such as google, facebook, github, etc. You can easily integrate Google using passport-google (deprecated), passport-google-oauth and passport-google-plus .
Let’s let go for an example.
storage.js
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 // http://tools.ietf.org/html/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() );
(...), but I think, why do we need sessions? I thought one of the goals of the tokens is that the server can be stateless?
When the client redirects the user to the user's authorization endpoint, an authorization transaction is initiated. To complete the transaction, the user must authenticate and approve the authorization request. Since this may include multiple HTTP request / response exchanges, the transaction is saved in the session.
Well, yes, but the session is used for the token negotiation process. Later, you perform authorization by sending a token in the authorization header to authorize each request using the received token.