OpenWhisk, Serverless, and Security — a POC

Before I begin, I want to be clear that what follows is a proof of concept. It should absolutely not be considered a recommendation, but rather a starting point for conversation. I’ve been thinking a lot lately about how one could use OpenWhisk along with a security model of some sort. Specifically, “Expose action so and so but only for authorized users.” Obviously “security” can imply a lot more, but in this initial post I’m going to keep my requirements a bit simpler.

  • Authenticate the user somehow.
  • Make OpenWhisk action Foo require a logged in user.

For authentication, I chewed on a couple of different things. In theory, I could setup a Cloudant database and build actions for the typical user register/login routine, but I really, really didn’t want to do that. Instead, I decided to finally take a look at Auth0. I’d heard of this service before but never heard a chance to actually play with it. Turns out, it was pretty easy. (I’ve got some more comments about Auth0, but I’ll save them for the end of the post.) I set up a new Auth0 application using their “Lock” client-side setup to handle authentication. I built an incredibly bad client-side demo with no real UI and — yeah — did I say it was bad? The front end consists of two buttons:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
</head>
<body>

<p>
<button id="loginBtn">Login</button>
</p>

<p>
<button id="testFooBtn">Test Foo</button>
</p>

<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script src="https://cdn.auth0.com/js/lock/10.14/lock.min.js"></script>
<script src="app.js"></script>
</body>
</html>

I’m including Auth0 code at the bottom there along with jQuery. My two buttons handle logging in as well as calling the action I’ll be demonstrating in a minute. The JavaScript code is mostly from the Auth0 code. Again, I want to be clear this is just some rough code to let me try stuff out.

let lock;
let idToken;

$(document).ready( () => {
console.log('lets do this');

lock = new Auth0Lock('asKI36rzsbOyY40DaGv6mPWODbexIO-R','raymondcamden.auth0.com');

$('#loginBtn').on('click',doLogin);

$('#testFooBtn').on('click',doTestFoo);

idToken = localStorage.getItem('id_token');
if(idToken) {
getProfile();
}

lock.on("authenticated", function(authResult) {
console.dir(authResult);
localStorage.setItem('id_token', authResult.idToken);
idToken = authResult.idToken;
getProfile();
});

});

function doLogin() {
lock.show();
}

function getProfile() {
lock.getProfile(idToken, function(error, profile) {
if (error) {
// Handle error
return;
}
// Display user information
//show_profile_info(profile);
console.dir(profile);
});
}

When the login button is clicked, I run the Lock API’s show method. This is where Auth0 really kicks butt. It handles the auth process completely and in the end, leaves me with a JWT (JSON Web Token) I can use to authenticate my later calls. The getProfile call is from Auth0’s sample code. I don’t actually use it for anything practical. Let me show you how this all looks.

First, the page as it loads by default (and again, this isn’t meant to be production-ready):

And here is how Auth0’s auth routine looks like:

By the way, Facebook and Google were arbitrary choices — you can tweak that (again, I’ll talk more about Auth0 at the end).

After logging in, I’m brought back to my web page and have access to my profile, but the important thing is the id_token value. That represents the JWT value. I can use this in my OpenWhisk action to authenticate the request.

JWT verification is rather easy. Here is the action I built:

/*
hard coded secret
*/
const secret = require('./creds.json');
const jwt = require('jsonwebtoken');

/*
args.token, passed in
*/
function main(args) {

return new Promise( (resolve, reject) => {

let decoded = jwt.verify(args.token, secret.key, (err,decoded) => {

if(err) {
console.log('err',err);
reject({
name:err.name,
message:err.message,
stack:err.stack
});
} else {
resolve({decoded:decoded});
}

});

});

}

exports.main = main;

The action needs two values, a key that is specific for my application, and loaded via a JSON file, and the JWT itself which is passed in. I require in the jsonwebtoken package which has a verify method I can run to verify the token is both valid for my application (based on the secret value) and a certain length of time. And that’s it. Seriously - nice and simple, Right? I called this action jwtverify.

So to bring this together, I need to build an action that will secured. I create the following, super simple action called foo.

function main(args) {

if(!args.name) args.name = 'Nameless';
return {
result:"Hello "+args.name
}

}

I pushed this up to OpenWhisk. I now have a generic “jwt verify” action and a random action I want to secure. How do I do that? With a sequence. I created a new action called secureFoo that was based on the sequence: jwtverify+foo. (Technically I made these all under a package called secblog just to organize the demo a bit.) The end result is an action that acts like an Express app with middleware.

To test, I exposed secureFoo via a REST API (not as a web action because CORS isn’t a simple addition yet). I then built this function to support that second button in my demo app:

function doTestFoo() {
$.get('https://3b1fd5b1-e8cc-4871-a7d8-cc599e3ef852-gws.api-gw.mybluemix.net/auth1/foo?token='+idToken+'&name=Ray').then((res) => {
console.log(res);
});
}

The URL you see there is the path I exposed for my REST API. The crucial bit is at the end where I pass the token. This gets passed to the first action in the sequence, which verifies it, and then it carries on to the second. The result is what you would expect:

Whew! Ok, done… kinda. Did you notice that my Foo action accepted an argument? I passed name=Ray in the URL, but it wasn’t reflected in the result. One important thing to remember about sequences is that the arguments sent as input only go to the first action. If you want any later action to work with input, they have to be passed along as the result of each previous action.

Since our first action is a generic “secure this process” type thing, one approach we could take is to simply pass along everything that was sent, except the initial token. I modified my verification code like so:

delete args.token; 
resolve(args);

And just like that — it works. I won’t bother with a new screen shot since it literally just changed from Nameless to Ray. One issue with this particular setup is that ‘token’ is - essentially - a reserved word now. As this is my app and I’m building the APIs, I can handle that.

So… that’s that. As I said, I’m definitely not sure how much sense this makes, but I’d love to hear folks opinions. Leave me a comment below! You can find the source code for everything I showed here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/auth1

Stop Reading

Or not. ;) As I said above, Auth0 is really, really freaking good. Everything about them just impressed me. Sign up was easy. They had a crap ton of samples, I mean, shoot, almost overwhelming in terms of sample code. One thing in particular I really freaking liked is that you can test social login and they will use their own apps until you specify your own. What I mean is — normally for social login you go to the site, make a new app, make note of the various IDs, go back to your code, copy them in, etc. etc. That’s not hard of course, but it’s boring. With Auth0, while testing only of course, you can just use their apps. I really dig that! Anyway, I hope to play with Auth0 a lot more in the future, and so far, I can definitely recommend it!


Originally published at www.raymondcamden.com on April 17, 2017.