Lambda@Edge allows you to run lambda functions in response to CloudFront events. In order to use a lambda function with CloudFront, you need to make sure that your function can assume edgelambda identity. I want to show you an easy way to do it with serverless.
Missing identity
As of right now, serverless framework has no native support for lambda@edge. There is a plugin though that allows you to associate your lambda functions with a CloudFront distribution’s behavior.
The plugin works great if you deploy and control both your lambda functions and its associations with the CloudFront distributions. You might, however, be deploying a global function that is to be used by different teams on different distributions. Here’s a good example - a function that supports redirecting / to /index.html deeper in the URL hierarchy than the site root.
Serverless allows you to define additional IAM role statements in iamRoleStatements block but doesn’t seem to have a shortcut for the iamRoleLambdaExecution. You can certainly configure your own custom IAM::Role but that’s a pretty involved excercise if all you want to achieve is this:
Easy way out
If you don’t define your own IAM::Role, serverless will create one for you. The easiest way to see how it looks is to run sls package, look inside your .serverless folder, and inspect the CloudFormation JSON that will orchestrate your deployment. Look for IamRoleLambdaExecution in the Resources group.
Serverless carries a template that it uses as a starting point to build the role definition. The good news is that serverless merges it into the list of other resources that you might have defined in your serverless.yml. Take a look at the code if you want to see how it does it.
The name of the roles seems to always default to IamRoleLambdaExecution (here and here). Knowing how lodash’s merge works, all we need to do now is to give our resources definition a little boost.
Recently I found myself integrating OAuth 2 into a React/node.js app. The web is full of blog posts and questions answered on how to do it, but I still had to scratch my head a few times to get everything right. This post is a simple step by step recipe on how to do it with Passport.
This wasn’t apparent to me from the beginning and from the simple examples that I looked at, but:
passport is designed to only facilitate the authentication process. In other words, the only route that should have passport middleware on it is the /login route where you would send your unauthenticated users to
Actually, you should also add the passport middleware to your callback route or just make /login your OAuth callback. That’s what I did. The OAuth strategy looks at ?code= in the URL to decide whether to initiate the authentication sequence or process the callback:
const tokenToProfile = async () => {} // <-- I will explain in step 9
const strategy = new OAuth2Strategy({ state: true, authorizationURL: process.env.AUTH_AUTHORIZATION_URL, tokenURL: process.env.AUTH_TOKEN_URL, clientID: process.env.AUTH_CLIENT_ID, clientSecret: process.env.AUTH_CLIENT_SECRET, callbackURL: process.env.AUTH_CALLBACK_URL, passReqToCallback: true// <-- I will explain in step 9 }, tokenToProfile);
passport.use(strategy);
Step 7. De/Serialize user
In order not to run the authentication sequence on every request, you would typically store the authenticated user ID in the session and then trust it for as long as the session is active. You need to implement serializeUser and deserializeUser to do it. Passport doesn’t do it automatically:
passport.serializeUser((user, done) => { // The user object in the arguments is the result of your authentication process // (see step 9)
done(null, user);
// If your user object is large or has transient state, // you may want to only store the user id in the session instead:
// done(null, user.user_id) });
passport.deserializeUser(async (user, done) => { // The user object in the arguments is what you have stored in the session
// If you stored the entire user object when you serialized it to session, // you can skip re-quering your user store on every request
user = await User.getUserByID(user.user_id);
done(null, user); });
Step 8. Tokens
OAuth 2 can send back access_token and it can also send the id_token. The latter is always a JWT token and the former is typically an opaque string.
Sometimes all you need is the access_token that you pass on to the back-end APIs. I, however, needed to authenticate the user and match the user’s identity with the application’s user record.
Two options:
Use the /userinfo endpoint with the access_token to retrieve the profile from your identity provider
Ask for the id_token and get profile attributes from there. To receive the id_token in the callback, you need to add scope=openid to your authorization request. If you need user’s email or additional attributes like name, for example, you will need to ask for more scopes (scope=openid email or scope=openid profile).
OAuth 2.0 is not an authentication protocol, apparently. Read the User Authentication article on oauth.net if you want to learn more. The id_token, claims, scopes, and /userinfo are all part of OpenID Connect.
Step 9. Retrieve Profile
When we set up the OAuth 2 strategy in step 6, we had to supply a tokenToProfile callback. If you read the documentation, you will see that it has the following signature:
1 2 3
function (accessToken, refreshToken, profile, cb) { // Note: no id_token passed in the arguments }
Don’t be surprised to always receive an empty object in profile:
OAuth 2 strategy for passport does not implement retrieval of the user profile
The latter requires you to not only ask for the id_token from your identity provider using scope=openid, but to also have it exposed to you by the OAuth 2 strategy in passport. To do so, you need to set passReqToCallback to true when you instantiate the strategy (we did in step 6), and then you can use a different signatue for your callback:
The easiest and the most effective way to logout a user is to destroy the session:
1 2 3
router.get('/logout', function (req, res) { req.session.destroy(() => res.redirect('/')); });
Step 11 (Bonus). Spoof Authentication
If you have gotten this far, I have a bonus step for you. I found it very helpful to be able to spoof authentication in local environment for development and testing.
First, the environment variable in my .env file to signal that the auth should be bypassed and to tell the app what user to run on behalf of: