This has probably been asked before, so a preemptive apology from me.
I built a site and I built an API. The API will also be used by a mobile app in the future. I own both so I'm pretty sure two and three legged OAuth aren't for me. The API has parts that are accessible to the world and other parts that are protected and require a user account. To keep things simple I've just gone with a https + Basic Auth solution (for now). It's all fine and good when testing requests manually to the API (I didn't write tests because I'm a bad person), things work as expected and Basic Auth is fine.
I'm trying to solve the flow of a user logging in with plaintext user and password, send that to the API to authenticate, the API just needs to say yes or no, yet all requests from the site (on behalf of a user) to the API should be signed in some way with their credentials for when they want to POST/GET/PUT/DEL one of the protected resources.
Out of all of the auth resources I've read I'm still confused as to what scheme to use. Storing the plaintext password on the site side so that I can base 64 encode it and send it over the wire seems bad, but it looks like that's what I'd have to do. I've read of digest auth but I'm not sure I get it. Any and all advice is welcome.
This is how I would handle this case;
POST the username and password as a plain text to your api using HTTPS of course.
Then validate it to your database, the best algorithm used nowadays to salt password is bcrypt.
If the user is not valid return 401, or whatever.
If the user is valid, return a JWT token with his profile signed with a Public Key algorithm.
Your fron-end knows the public key so it can decode the JWT but it can't generate a new one.
For every request that needs authentication, you attach an Authentication header, with Bearer [JWT]
A middleware in the backend reads this header and validate it with the private key.
Don't be affraid of JWT there are plenty of implementations for every language and framework and is easier than you might think. A lot of applications are already using JWT already even Google.
Auth0 is an authentication broker that can validate against any identity provider or custom database, and returns JWTs. It provides a clientID that can be used to decode the profile in the front end and a secret to validate the tokens in the backend as well as client side library to do this.
Disclaimer: I work for auth0.
Update: Since you mention node.js and express in comments I will give an example in this technology.
var http = require('http');
var express = require('express');
var jwt = require('jsonwebtoken'); //https://npmjs.org/package/node-jsonwebtoken
var expressJwt = require('express-jwt'); //https://npmjs.org/package/express-jwt
var secret = "this is the secret secret secret 12356";
var app = express();
app.configure(function () {
this.use(express.urlencoded());
this.use(express.json());
this.use('/api', expressJwt({secret: secret}));
});
//authentication endpoint
app.post('/authenticate', function (req, res) {
//validate req.body.username and req.body.password
//if is invalid, return 401
var profile = {
first_name: 'John',
last_name: 'Foo',
email: 'foo#bar.com',
id: 123
};
var token = jwt.sign(profile, secret, {
expiresInMinutes: 60*5
});
res.json({
token: token
});
});
//protected api
app.get('/api/something', function (req, res) {
console.log('user ' + req.user.email + ' is calling /something');
res.json({
name: 'foo'
});
});
//sample page
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
http.createServer(app).listen(8080, function () {
console.log('listening on http://localhost:8080');
});
This is an express application with one endpoint that validates username and password. If the credentials are valid it returns a JWT token with the full profile, with expiration 5 hours.
Then we have an example endpoint in /api/something but since I've a express-jwt middleware for everything on /api it requires a Authorization: Bearer header with a valid token. The middleware not only validates the token but also parses the profile and put it on req.user.
How to use this client-side? This is an example with jquery:
//this is used to parse the profile
function url_base64_decode(str) {
var output = str.replace("-", "+").replace("_", "/");
switch (output.length % 4) {
case 0:
break;
case 2:
output += "==";
break;
case 3:
output += "=";
break;
default:
throw "Illegal base64url string!";
}
return window.atob(output); //polifyll https://github.com/davidchambers/Base64.js
}
var token;
//authenticate at some point in your page
$(function () {
$.ajax({
url: '/authenticate',
type: 'POST',
data: {
username: 'john',
password: 'foo'
}
}).done(function (authResult) {
token = authResult.token;
var encoded = token.split('.')[1];
var profile = JSON.parse(url_base64_decode(encoded));
alert('Hello ' + profile.first_name + ' ' + profile.last_name);
});
});
//send the authorization header with token on every call to the api
$.ajaxSetup({
beforeSend: function(xhr) {
if (!token) return;
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
}
});
//api call
setTimeout(function () {
$.ajax({
url: '/api/something',
}).done(function (res) {
console.log(rest);
});
}, 5000);
First, I've an authenticate call with the username and password, I can decode the profile in the JWT to get the user profile and I also save the token to use in every request later on.
The ajaxSetup/beforeSend trick adds the header for every call. So, then I can make a request to /api/something.
As you can imagine this approach doesn't use cookies and sessions so it works out of the box in CORS scenarios.
I'm a big fan of passport.js and I've contributed a lot of adapters and fixes for some other adapter but for this particular case I wouldn't use it.
I've been thinking about a similar scenario lately; here's what I did:
SSL + Basic Auth
In the DB (on the API side), generate a random salt (per user), and save the salt and the hashed (password + salt). When a request arrives, throw on the salt and hash it, then compare to what you've saved
Send the password in plaintext - you are using SSL so I think this is okay (this is the part I am most uncertain of)
I don't have a great reason for recommending this but in case you have a reason to use it:
.4. Attach a timestamp to every request and have them expire after a couple of minutes.
The reason you should save salted-and-hashed passwords in your DB is in case someone steals your DB.
Basically I'm putting a lot of faith into SSL, and what I've read tells me that's okay.
Related
We've got scripts on Bing to automatically adjust ad bids based on ad performance and client goals, which are stored in a Google spreadsheet.
We had a contractor set this up initially, and it worked. But I guess that the contractor was using a temp Google account and when it went away the bidders stopped working. Because it did work before, it's likely a configuration error on my part that's breaking it now, but the contractor pointed us to the steps I was already following to no avail (https://learn.microsoft.com/en-us/advertising/scripts/examples/authenticating-with-google-services#option2).
Stuff already tried
double checked for errant whitespace around the client ID and client secret
created new client secrets
created new client IDs
made sure that the project name, application name, and OAuth client id name were all the same
created whole new projects from scratch (configured to match the article cited above) to see if that would kick something loose
tried a different token URL (https://oauth2.googleapis.com/token) that appears in the client_secret JSON downloaded from Google
function main() {
const credentials = {
accessToken: '',
client_id: 'REDACTED.apps.googleusercontent.com', // from Google developer console
client_secret: 'REDACTED', // from Google developer console
refresh_token: 'REDACTED' // created at https://developers.google.com/oauthplayground
};
var access_token = '';
if (credentials.accessToken) {
access_token = credentials.accessToken;
}
var tokenResponse = UrlFetchApp.fetch('https://www.googleapis.com/oauth2/v4/token', { method: 'post', contentType: 'application/x-www-form-urlencoded', muteHttpExceptions: true, payload: { client_id: credentials.clientId, client_secret: credentials.clientSecret, refresh_token: credentials.refreshToken, grant_type: 'refresh_token' } });
var responseCode = tokenResponse.getResponseCode();
var responseText = tokenResponse.getContentText();
if (responseCode >= 200 && responseCode <= 299) {
access_token = JSON.parse(responseText)['access_token'];
}
throw responseText;
// use the access token to get client targets from the spreadsheet
A JSON encoded access token is the expected response, but instead, we get HTTP 400 with the message "The OAuth client was not found."
Manually creating an access token on the OAuth playground (https://developers.google.com/oauthplayground) works as a stopgap, but this should work. This has worked. :P
The fix in this case switching the Application Type on console.developers.google.com > Credentials > OAuth consent screen to Internal instead of Public.
That wasn't in the steps provided by Microsoft, and I'm not sure if that will have implications down the road, but at least we're off the manual process for now.
I am working on a Web API where I implemented JWT based authentication. I am not using neither PasswordJS middlware nor Oauth protocol. Its basically JWT npm which I use to sign and verify tokens.
The whole concept of token are pretty clear, but I very much confused with the term 'token scheme' and cannot understand what it is used for.
What I would like to understand is: do I need to use some sort or custom 'JWT' scheme and validate it when token is send back to server for further requests, or this concept is used only by Oauth, and what I need is only send the plain token?
var accessToken = jwt.sign({
userID: user.id,
isAdmin: user.isAdmin
}, config.userSecret, {
expiresIn: 600
});
res.json({
success: true,
user: {
id: user._id,
name: user.name,
username: user.username,
accessToken: 'JWT ' + accessToken,
}
});
jwt.verify(accessToken, secret, function(err, token){...}); //throws error when token is passed with the custom scheme
Exactly what scheme you are using isn't that important in this case, because you are parsing the content of the Authorization header manually anyway.
Basically, the token is sent from the client to the server on an HTTP header called Authorization. In front of the token you put the name of the scheme. So the Authorization header might look something like this:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
(The list of allowed names is here. For JWT it's usually Bearer. You are technically not following the OAuth 2.0 bearer scheme according to RFC6749, but it's usually called Bearer anyway.)
You have to manually take the token (ey...) and verify it with jwt.verify() to get its payload.
const headerExists = req.headers.authorization
if (headerExists) {
let token = req.headers.authorization.split(' ')[1];
jwt.verify(token, auth.secretjwtkey, function (err, decoded) {
if (err) {
res.status(HttpStatus.UNAUTHORIZED).json('Unauthorized');
} else if (decoded.role === 'admin') {
next();
} else {
res.status(HttpStatus.UNAUTHORIZED).json('Unauthorized');
}
})
} else {
res.status(HttpStatus.FORBIDDEN).json('No token');
}
You can see from the example middleware above that I don't care about the Bearer string on the Authorization header, only the token itself. You could, of course, check that it actually was Bearer and not something else though.
So the moral of the story is that:
You send the token from client to the server on the Authorization header. You have to set up the front-end so that happens.
You prepend Bearer in front of the token (or one of the other in the allowed list, but bearer is recommended).
You decode the token by reading the second part of the string that is on the Authorization header and then feed it to jwt.verify().
See here for more details.
I am trying to revoke oauth2 tokens using the stormpath API. Server-side authentication is performed using stormpath + express. Here is my request.
function revokeOauthTokens(params) {
// Revoke the oauth2 access. and refresh tokens
var oauthLogoutReq = {
method: 'POST',
url: params.apiBaseUrl + '/logout',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: 'grant_type=refresh_token&refresh_token='
+ params.oauth_tokens.refresh_token
}
return $http(oauthLogoutReq);
}
Where apiBaseUrl is my nodejs base url and ouath_tokens contains the response granted by a request to /oauth/token endpoint.
Looking at the documentation at the following links leaves me confused.
http://docs.stormpath.com/nodejs/express/latest/logout.html
http://docs.stormpath.com/guides/token-management/
Thanks.
This is a great question. As you’ve seen, express-stormpath is using secure, http-only cookies for token storage, and this doesn’t work for Cordova, Electron, etc, where cookie storage isn't implemented to spec. The alternative is local storage, or some other storage API that is provided to you (hopefully a secure one!).
The express-stormpath library does provide a /logout route, and it does revoke tokens, but it’s looking for the tokens in cookies. We need to add a new route, likely /oauth/revoke, to support explicit token revocation.
This is pretty easy to add right now as a custom route handler, and I’m including a link below. But please be aware that express-stormpath uses local token validation by default. This is done for speed (no roundtrip to our API) but the caveat is that your local server will NOT know that the tokens have been revoked, and can technically still be used for authentication if a malicious third party has stolen them from your client. If this is a concern you want to to address, you should opt-in to stormpath validation, which will always require a check against our token database. This is documented here:
http://docs.stormpath.com/nodejs/express/latest/authentication.html#token-validation-strategy
All that said, here is the route handler that you could wire up as /oauth/revoke, and have your Electron client use it to revoke the tokens when the user logs out:
'use strict';
var revokeToken = require('express-stormpath/lib/helpers/revoke-token');
function defaultResponder(res, err) {
if (err) {
console.error(err); // or your system logger
return res.status(err.status || 400).json({
message: err.developerMessage || err.message
});
}
res.end();
}
/**
* Implements the expected behavior of the /oauth/revoke endpoint, and requires
* that token_type be defined. This assumes that you are using the express-stormpath
* module, so that your Stormpath client and configuration context is available.
*
* #param {Object<ExpressRequest>} req Express JS Request
* #param {Object<ExpressResponse>} res Express JS Response
*/
function revokeTokens(req, res){
var client = req.app.get('stormpathClient');
var config = req.app.get('stormpathConfig');
var secret = config.client.apiKey.secret;
var token = req.body.token;
var token_type = req.body.token_type;
if (!token || ! token_type) {
defaultResponder(res, {
message: 'token and token_type fields are required'
});
}
if (token_type === 'access_token') {
revokeToken.revokeAccessToken(client, token, secret, defaultResponder.bind(null, res));
} else if (token_type === 'refresh_token') {
revokeToken.revokeRefreshToken(client, token, secret, defaultResponder.bind(null, res));
} else {
defaultResponder(res, {
message: 'invalid token_type'
});
}
}
module.exports = revokeTokens;
If you find that you don't want to use express-stormpath and would like to use something more direct, you can drop down to the Stormpath Node SDK and use it for token revocation:
https://docs.stormpath.com/nodejs/jsdoc/AccessToken.html
Or you can make DELETE requests directly against our API:
https://docs.stormpath.com/rest/product-guide/latest/auth_n.html#revoking-access-and-refresh-tokens
In both cases, you would be doing that from your server, not the Electron application.
I hope this helps!
-Robert
I'm trying to set up a Chrome Extension that uses chrome.identity.getAuthToken to get the logged in user's auth token and then use that to authenticate with an Express server using Passport and the passport-google-token strategy.
getAuthToken is giving me the token but when it's sent to my server, I'm getting a 401 unauthorized error.
I'm pretty new to Passport and to token based authorisation in general, so I'm not sure if I've made a mistake or misunderstood how it's meant to work.
My chrome extension does this:
chrome.identity.getAuthToken({"interactive": true}, function(token){
var url = "http://localhost:30000/auth/chrome";
var x = new XMLHttpRequest();
x.open("GET", url);
x.setRequestHeader('Authorization', "Bearer " + token);
x.send();
});
and the token is being correctly passed into my callback.
I set up my Express server and Passport strategy like this:
import * as express from "express";
import * as passport from "passport";
const GoogleTokenStrategy = require("passport-google-token").Strategy;
// set up Express and Passport...
passport.use(new GoogleTokenStrategy({
clientID: --client id--,
clientSecret: --client secret--
}, (accessToken, refreshToken, profile, done) => {
return done(null, profile);
}));
app.get('/auth/chrome', passport.authenticate("google-token"), (req, res) => {
res.send(req.user);
});
The client ID and secret come from the credentials I've set up at the Google API Manager:
If anyone can point me to what else I need to do or what I'm doing wrong, it would be much appreciated.
There were two reasons this was failing for me.
The first, which I realised when I stepped through some of the passport-google-token code, is that it fails if req.body is undefined. I fixed that by adding the body-parser middleware.
The main problem though was the way I was sending the access token in the header. I had copied x.setRequestHeader('Authorization', 'Bearer ' + token); from one of the Google sample apps but it actually needed to be sent as:
x.setRequestHeader('Access_token', token);
or in the query string as:
var url = "http://localhost:30000/auth/chrome?access_token=" + token;
I have been wondering for quite a while how I would go about authenticating users using Backbone because I have been reading a few articles about it and a lot of them are talking about tokens and keys.. But I just want to be able to sign in a user and register a user like you would normally.
I was thinking that on the web app start up there would be a request to the route '/me' and then the server gives the user back appropriate information if he/she is logged in.
Like if the route came back with {loggedIn: false} the backbone router would send the user to the login/register pages only. But if it came back with a users profile information then it would obviously mean he had a session.
Is this an okay way of going back user authentication when using Backbone?
Short answer: wire up $.ajax to respond to 401 (Unauthorized) status codes.
Long answer: We're consuming a RESTful api from a single page website. when the server detects an unauthorized request, it just returns a 401. The client will redirect to /login?#requested/resource.
/login will prompt for authorization (redirect to google's oath server in our case) then add an authorization cookie and redirect to the originally requested #requested/resource
we're also sending the auth cookie on every $.ajax request.
Hopefully this is helpful.
define(
[
'jquery',
'jquery.cookie'
],
function ($) {
var redirectToLogin = function () {
var locationhref = "/login";
if (location.hash && location.hash.length > 0) {
locationhref += "?hash=" + location.hash.substring(1);
}
location.href = locationhref;
};
var $doc = $(document);
$doc.ajaxSend(function (event, xhr) {
var authToken = $.cookie('access_token');
if (authToken) {
xhr.setRequestHeader("Authorization", "Bearer " + authToken);
}
});
$doc.ajaxError(function (event, xhr) {
if (xhr.status == 401)
redirectToLogin();
});
});