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
Related
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 using 'googleapis' npm package to do token based google authentication.
I am redirected to '/api/auth/success/google' route inside express after google provided authentication and redirects us to the uri stated in google app credentials.
The problem I am facing is that ,I have retrieved the tokens on server side,but I am unable to send those tokens to client side for them to be saved in cookies.
The problem I am facing is because,'/api/auth/success/google' is redirected from google side and not an ajax call from client side.So if I send the tokens back in res,where will it redirect.Also please suggest a way to redirect from server side to client side,along with access_token.
server side code.
//Route reached after google successful login/authentication
app.get('/api/auth/success/google',function(req,res){
console.log("inside redirect");
var code = req.query.code;
oauth2Client.getToken(code, function(err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
if(!err) {
oauth2Client.setCredentials(tokens);
}
res.sendFile('./index.html');
});
})
Client side call
//Google login requested from this function
googleLogin(){
event.preventDefault();
$.ajax({
type : 'POST',
url : baseURL + 'api/authenticate/google',
success: (function(data) {
if (data.redirect) {
document.location.href = data.redirect;
}
}).bind(this)
});
}
//Route handling request of google access
app.post('/api/authenticate/google',function(req,res){
// generate a url that asks permissions for Google+ and Google Calendar scopes
var scopes = [
googlecredentials.SCOPE[0],
googlecredentials.SCOPE[1]
];
var url = oauth2Client.generateAuthUrl({
access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token)
scope: scopes // If you only need one scope you can pass it as string
});
res.send({ redirect: url });
})
//Google App Credentials
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2(googlecredentials.CLIENT_ID, googlecredentials.CLIENT_SECRET, googlecredentials.REDIRECT_URL);
googlecredentials.CLIENT_ID - 858093863410-j9ma1i7lgapupip1ckegc61plrlledtq.apps.googleusercontent.com
REDIRECT_URL - http://localhost:3000/api/auth/success/google where localhost:3000 runs server side
If you send the redirect URL back in the res, the client-side should be able to check for the presence of the redirect URL in the response, and if it exists, push your user to that URL.
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'm new to office 365 and having problem with accessing rest api.
I'm trying to test the rest api of Calendar and Mail API, so I decided to use Postman. However, to test those APIs, I need an access token in Authorization header. To figure out how to get a token, I decided to get the sample project here , configure, run and sign in on this local site to get the token cached in local storage and use that token for further requests in Postman. However, all requests I tested returned '401 unauthorized request'.
What I did:
Register a new app on Azure ADD associated with O365 account
Add full app permissions and delegated permissions.
Update 'oauth2AllowImplicitFlow' to true in manifest file.
Clone sample project
In app.js, I change the alter the content of config function as following
function config($routeProvider, $httpProvider, adalAuthenticationServiceProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
controller: 'HomeController',
controllerAs: 'home',
requireADLogin: true
})
.otherwise({
redirectTo: '/'
});
// The endpoints here are resources for ADAL to get tokens for.
var endpoints = {
'https://outlook.office365.com': 'https://outlook.office365.com'
};
// Initialize the ADAL provider with your tenant name and clientID (found in the Azure Management Portal).
adalAuthenticationServiceProvider.init(
{
tenant: 'mytenantname.onmicrosoft.com',
clientId: '<my cliend Id>',
endpoints: endpoints,
cacheLocation: 'localStorage'
},
$httpProvider
);
};
Then I ran the app, it sign me in just fine and I can also get the token, but that token is also unauthorized to request.
I decoded the token and saw the value of 'aud', it didn't return "https://outlook.office365.com/". In this url, the author said that "This should be "https://outlook.office365.com/" for the Mail, Calendar, or Contacts APIs"
So what did I miss ?
How you call the Office 365 API in AngularJS?
When signing the user in, you will only get the id_token to authenticate the user.
The aud of id_token is the tenant id (GUID).
To call the Office 365 API, you need to use AugularJS http request.
Here is a sample of sending email using Microsoft Graph API in AngularJS:
// Build the HTTP request to send an email.
var request = {
method: 'POST',
url: 'https://graph.microsoft.com/v1.0/me/microsoft.graph.sendmail',
data: email
};
// Execute the HTTP request.
$http(request)
.then(function (response) {
$log.debug('HTTP request to Microsoft Graph API returned successfully.', response);
response.status === 202 ? vm.requestSuccess = true : vm.requestSuccess = false;
vm.requestFinished = true;
}, function (error) {
$log.error('HTTP request to Microsoft Graph API failed.');
vm.requestSuccess= false;
vm.requestFinished = true;
});
Before calling the API, ADAL.js will acquire another token - access token which you can used to send the email.
UPDATE#1
I also downloaded the sample you mentioned. To run this sample, please ensure you have the Exchange Online > Read and writer user mail Permission assigned in your application.
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.