AWS lambda serverless website session maintaining - node.js

I developed a website using node.js as back-end.
Recently I am trying to make it serverless and deploy to lambda.
I will re-write most of my code but just haven't figured out how to maintain the session after user logged in. I was using "express-session" module and the session data is all recorded in the database.
To be honest I don't have a very deep understanding on sessions.
I searched on google and did not find what I need.
Does anyone have some sample code on maintaining sessions using lambda?
or any resources. Thanks a lot!

In the Amazon Cognito Identity SDK for Javascript, check in particular the use case 16, it shows how to retrieve the Cognito current user. You can use this function to pass from page to page the current user attributes.
var poolData = {
UserPoolId : '...', // Your user pool id here
ClientId : '...' // Your client id here
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var cognitoUser = userPool.getCurrentUser();
if (cognitoUser != null) {
cognitoUser.getSession(function(err, session) {
if (err) {
alert(err);
return;
}
console.log('session validity: ' + session.isValid());
// other AWS actions ...
});
}

There are multiple mechanisms available in HTTP to maintain session
state within web applications, such as cookies (standard HTTP header),
URL parameters, URL arguments on GET requests, body arguments on POST
requests, such as hidden form fields (HTML forms), or proprietary HTTP
headers.
Source: Session Management Cheat Sheet
AWS Lambda has nothing to do with session management unless you want to re-invent the wheel and write Lambda functions that store/retrieve session variables from the database, in which case I'd recommend that you use Amazon Cognito for session management. See Amazon Cognito Identity SDK for JavaScript.

Either you use Cognito or your own way of session management, beware that lambda calls share their runtime and share static state between them. Make sure that your design considers this fact and architect your session sharing accordingly.
https://www.linkedin.com/pulse/does-lambda-call-share-any-commonstate-santhosh-gandhe/

Related

CognitoIdentityServiceProvider.signUP() doesn't accept user pool id?

In the aws-sdk cognito documentation there is a function listed called signUp() that quote "Registers the user in the specified user pool and creates a user name, password, and user attributes." However, there is no parameter for a user pool Id. How exactly does one specify the user pool they want to add to? At first glance it thought maybe it was just missing in the documentation, but I tried adding UserPoolId as a property in the parameters object, to which it responded with an error about an unexpected field. There is also no function parameter to accept the pool id. My only other guess was that maybe the CognitoIdentityServiceProvider object accepted it in its constructor, but that also does not appear to be the case. I am aware that the API also provides the function AdminCreateUser() to add users, but don't want to use it if there's a better way.
documentation here https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityServiceProvider.html#signUp-property
Any ideas?
In Amazon Cognito, the User Pool ID is considered to be a sensitive piece of information, and it is used only in Admin API calls. The SignUp API call is not AWS SigV4 Signed, and it is meant to run on the browser side instead of the server side.
From the App Client ID, Cognito implicitly understands which User Pool you are referring to in your code. Hence, you can use the code in the documentation, and users will get added to your User Pool without the User Pool ID being a parameter in the API call.
Specify the App Client Id from the User Pool - not the User Pool Id. Create an App Client on the User Pool if you don't have one yet.
https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html
The App Client Id is not considered secret from the AWS Cognito docs (but of course App Client Secret is - never include that anywhere on the client side). I've scratched out my App Client Id just to not broadcast here.
import {
CognitoIdentityServiceProvider
} from 'aws-sdk'
// make sure to use the same region as your user pool
const cognitoIdentityServiceProvider =
new CognitoIdentityServiceProvider({region: 'us-west-2'})
try {
const signUpResp =
await cognitoIdentityServiceProvider.signUp({
// Not UserPool ID but the ID for the "App Client" you have created on the User Pool
ClientId: '5orf...i67r',
Password: pw,
Username: email // i'm using email address for Username
}
).promise()
console.log(signUpResp)
} catch(e) {
alert(e.message || JSON.stringify(e));
}

Custom node.js api's authorization with aws cognito permission rules

I'm able to register and authenticate users via amazon-cognito-identity-js
and aws-sdk and the process to implement it was pretty much straightforward and simple. Now I have few APIs built in node.js, how would I authorize these APIs using Cognito policies (i.e permissions) and wrap them around logged in user where I'm already able to get idToken, accessToken and refreshToken.
Other things I came across while reading the documentation and watching video tutorials are aws-lambda and aws-api-gateway. I understood that aws-lambda is a way to define methods that can communicate with no-sql database (i.e AWS dynamo) and api-gateways to access those lambda functions and interact with DB and that all is serverless.
My question is, what if I have my own APIs written in Node.js and I need to integrate Cognito user pools with it and to protect those resources using AWS Cognito policies, user groups and roles, is it possible? Please guide me in this regard.
You can use the same authentication token which you get when you authorize the user to authorize your node.js APIs too. It can be done in many ways. One of the ways can be calling a isAuthenticated() method in each constructor which will check if there is a valid token or not.
export interface LoggedInCallBack{
isLoggedIn(message:string, loggedIn:boolean):void;
}
isAuthenticated(callBack : LoggedInCallBack){
if(callBack==null){
throw("User login service call cannot be null");
}
console.log("Is authneticated");
var cognitoUser = this.cognitoUtil.getCurrentUser();
console.log("Is cognito user empty? " + cognitoUser);
if(cognitoUser!=null){
cognitoUser.getSession(function (err, session){
if(err){
console.log("Couldn't get session:" + err);
callBack.isLoggedIn(err, false);
}
else{
console.log("Session is: " +session);
callBack.isLoggedIn(err, true);
}
})
}
else{
callBack.isLoggedIn("Cannot retrieve the current user", false);
}
}

Authenticating a Google Cloud Function as a service account on other Google APIs

I have an HTTP-triggered function running on Google Cloud Functions, which uses require('googleapis').sheets('v4') to write data into a docs spreadsheet.
For local development I added an account via the Service Accounts section of their developer console. I downloaded the token file (dev-key.json below) and used it to authenticate my requests to the Sheets API as follows:
var API_ACCT = require("./dev-key.json");
let apiClient = new google.auth.JWT(
API_ACCT.client_email, null, API_ACCT.private_key,
['https://www.googleapis.com/auth/spreadsheets']
);
exports.myFunc = function (req, res) {
var newRows = extract_rows_from_my_client_app_request(req);
sheets.spreadsheets.values.append({
auth: apiClient,
// ...
resource: { values:newRows }
}, function (e) {
if (e) res.status(500).json({err:"Sheets API is unhappy"});
else res.status(201).json({ok:true})
});
};
After I shared my spreadsheet with my service account's "email address" e.g. local-devserver#foobar-bazbuzz-123456.iam.gserviceaccount.com — it worked!
However, as I go to deploy this to the Google Cloud Functions service, I'm wondering if there's a better way to handle credentials? Can my code authenticate itself automatically without needing to bundle a JWT key file with the deployment?
I noticed that there is a FUNCTION_IDENTITY=foobar-bazbuzz-123456#appspot.gserviceaccount.com environment variable set when my function runs, but I do not know how to use this in the auth value to my googleapis call. The code for google.auth.getApplicationDefault does not use that.
Is it considered okay practice to upload a private JWT token along with my GCF code? Or should I somehow be using the metadata server for that? Or is there a built-in way that Cloud Functions already can authenticate themselves to other Google APIs?
It's common to bundle credentials with a function deployment. Just don't check them into your source control. Cloud Functions for Firebase samples do this where needed. For example, creating a signed URL from Cloud Storage requires admin credentials, and this sample illustrates saving that credential to a file to be deployed with the functions.
I'm wondering if there's a better way to handle credentials? Can my
code authenticate itself automatically without needing to bundle a JWT
key file with the deployment?
Yes. You can use 'Application Default Credentials', instead of how you've done it, but you don't use the function getApplicationDefault() as it has been deprecated since this Q was posted.
The link above shows how to make a simple call using the google.auth.getClient API, providing the desired scope, and have it decide the credential type needed automatically. On cloud functions this will be a 'Compute' object, as defined in the google-auth-library.
These docs say it well here...
After you set up a service account, ADC can implicitly find your
credentials without any need to change your code, as described in the
section above.
Where ADC is Application Default Credentials.
Note that, for Cloud Functions, you use the App Engine service account:
YOUR_PROJECT_ID#appspot.gserviceaccount.com, as documented here. That is the one you found via the FUNCTION_IDENTITY env var - this rather tripped me up.
The final step is to make sure that the service account has the required access as you did with your spreadsheet.

What authentication strategy to use? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
Recently I have been reading up on OAuth2, OpenID Connect etc. But still very lost at what to use when and how to implement it. I am thinking of using NodeJS for now.
Lets say I want to create a blog service. This service will expose API's for clients to use. "Clients" include an admin CMS. I am thinking it will be nice to decouple my server and client (UI). I can change the UI without touching the server. These clients are likely going to be single page web applications.
Ok 1st question: In this example, should I be using OAuth2? Why? Isit just because I am authorizing the admin app to access by blog?
Since its SPA's, I think the right strategy is OAuth2 Implicit Flow?
For each app, eg. admin cms, I will have to generate an AppID which is passed to the auth server. No app secret is required correct?
Isit possible to use google login in this case (instead of username/password)? Does OpenID connect do this?
How do I implement all these in NodeJS? I see https://github.com/jaredhanson/oauth2orize, but I do not see how to implement the implicit flow.
I do see an unofficial example https://github.com/reneweb/oauth2orize_implicit_example/blob/master/app.js, but what I am thinking is why is sessions required? I thought one of the goals of tokens is so that server can be stateless?
I am also wondering, when should I use API key/secret authentication?
Let's examine your questions
Should I be using OAuth2? Why?
A: Well, as today the old OpenId 2 authentication protocol has been marked as obsolete (November 2014) and OpenId Connect is an identity layer built on top of OAuth2 so the real question is if is important for you and your business to know and verify the identity of your users (the authentication part). If the answer is "yes" then go for OpenId Connect otherwise you can choose any of the two, the one you feel more comfortable with.
Since its SPA's, I think the right strategy is OAuth2 Implicit Flow?
A: Not really. You can implement any strategy when using a SPA, some takes more work than others and greatly depends on what are you trying to accomplish. The implicit flow is the simplest but it does not authenticate your users since an access token is issued directly.
When issuing an access token during the implicit grant flow, the authorization server does not authenticate the client. In some cases, the client identity can be verified via the redirection URI used to deliver the access token to the client.
I would not recommend this flow for your app (or any app that needs a decent level of security1).
If you want to keep it simple you should use Resource Owner Grant flow with an username and password but again there is nothing that prevents you of implementing the Authorization Code Grant flow especially if you want to allow third parties apps to use your service (which in my opinion is a winning strategy) and it will be relatively more secure than the others since it requires explicit consent from the user.
For each app, eg. admin cms, I will have to generate an AppID which is passed to the auth server. No app secret is required correct?
A: Yes that is correct but the client_secret can be used to add an extra layer of security to the token endpoint in the resource owner flow when you can't use Basic authentication, this is not required in any other flow.2 3
The authorization server MUST:
require client authentication for confidential clients or for any
client that was issued client credentials (or with other
authentication requirements),
authenticate the client if client authentication is included, and
validate the resource owner password credentials using its
existing password validation algorithm.
and
Alternatively, the authorization server MAY support including the client credentials in the request-body (...) Including the client credentials in the request-body using the two parameters is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize the HTTP Basic 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 connect do this?
A: Yes, is possible to use google login in which case you are just delegating the authentication and authorization job to the google servers. One of the benefits of working with an authorization server is the ability to have a single login to access other resources without having to create a local account for each of the resources you want to access.
How do I implement all these in NodeJS?
Well you started with the right foot. Using oaut2horize is the most simple way to implement an authorization server to issue tokens. All other libraries I tested were too complicated of use and integrate with node and express (disclaimer: this is just my opinion). OAuthorize plays nicely with passport.js(both from the same author) which is a great framework to enforce the authentication and authorization with over 300+ strategies like google, facebook, github, etc. You can easily integrate google using passport-google(obsolete), passport-google-oauth and passport-google-plus.
Let's go for the example
storage.js
// An array to store our clients. You should likely store this in a
// in-memory storage mechanism like Redis
// you should generate one of this for any of your api consumers
var clients = [
{id: 'as34sHWs34'}
// can include additional info like:
// client_secret or password
// redirect uri from which client calls are expected to originate
];
// An array to store our tokens. Like the clients this should go in a memory storage
var tokens = [];
// Authorization codes storage. Those will be exchanged for tokens at the end of the flow.
// Should be persisted in memory as well for fast access.
var codes = [];
module.exports = {
clients: clients,
tokens: tokens,
codes: codes
};
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
// https://www.rfc-editor.org/rfc/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 what I am thinking is why is sessions required? I thought one of the goals of tokens is so that server can be stateless?
When a client redirects a user to user authorization endpoint, an authorization transaction is initiated. To complete the transaction, the user must authenticate and approve the authorization request. Because this may involve multiple HTTP request/response exchanges, the transaction is stored in the session.
Well yes, but the session is used for the token negotiation process. Later you enforce authorization sending the token in an Authorization header to authorize each request using the obtained token.
In my experience, OAuth2 is the standard way of securing APIs. I'd recommend using OpenID Connect though as it adds authentication to OAuth2's otherwise authorization-based spec. You can also get Single-Sign-On between your "clients".
Since its SPA's, I think the right strategy is OAuth2 Implicit Flow?
De-coupling your clients and servers is a nice concept (and I'd generally do the same too) however, I'd recommend the authorization code flow instead as it doesn't expose the token to the browser. Read http://alexbilbie.com/2014/11/oauth-and-javascript/. Use a thin server-side proxy instead to add the tokens to the request. Still, I generally avoid using any server-generated code on the client (like JSPs in java or erb/haml in rails) since it couples the client to the server too much.
For each app, eg. admin cms, I will have to generate an AppID which is passed to the auth server. No app secret is required correct?
You'll need a client ID for implicit flow. If you use authorization code flow (recommended), you'll need both an ID and secret but the secret will be kept in the thin server-side proxy rather than a client-side only app (since it can't be secret in that case)
Is it possible to use google login in this case (instead of username/password)? Does OpenID connect do this?
Yes. Google uses openid connect
How do I implement all these in NodeJS? I see https://github.com/jaredhanson/oauth2orize, but I do not see how to implement the implicit flow.
A nice thing about openid connect is that (if you use another provider like google), you don't have to implement the provider yourself and you'll only need to write client code (and/or utilize client libaries). See http://openid.net/developers/libraries/ for different certified implementations. See https://www.npmjs.com/package/passport-openidconnect for nodejs.

Oauth2 flow without redirect_uri

I am creating an Android/iOS app which communicates with a Node.js server and would like to identify them securely on my server using Google (and/or Facebook) and OAuth2. I've looked at the following documentation: https://developers.google.com/+/web/signin/server-side-flow
I do not need authorization, I only need authentication (I only want to make sure that the person calling my Node.js service is the person they say they are). To achieve this, if I understand properly, I have to let the user log in using Google on the client side, this will give them an authorization_code which they can then give to my server. My server can then exchange that code for an access_token, and therefore retrieve information about the user. I am then guaranteed that the user is the person they say they are.
The Google documentations (link above) says: "In the Authorized redirect URI field, delete the default value. It is not used for this case.", however, for my server to exchange the authorization_code for an access_token, it needs to provide a redirect_uri, am I missing something?
The redirect_uri is useless for Unity games, for instance (since logging in with Google simply opens a new "window", which is closed when logged in, no redirection involved).
TL;DR
How do you use OAuth2 to authenticate users between my client and my server without redirection?
TL;DR How do you use OAuth2 to authenticate users between my client and my server without redirection?
You can't. OAuth requires that the user is directed to an authorization (and possibly login) screen, and then redirected back to your app.
EDIT 20/12/22. See comment below regarding latest status
Have you looked at this documentation? https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi
Choosing a redirect URI
When you create a client ID in the Google Developers Console, two
redirect_uris are created for you: urn:ietf:wg:oauth:2.0:oob and
http://localhost. The value your application uses determines how the
authorization code is returned to your application.
http://localhost
This value signals to the Google Authorization Server that the
authorization code should be returned as a query string parameter to
the web server on the client. You may specify a port number without
changing the Google Developers Console configuration. To receive the
authorization code using this URL, your application must be listening
on the local web server. This is possible on many, but not all,
platforms. If your platform supports it, this is the recommended
mechanism for obtaining the authorization code.
I had this problem and it took me ages to find the "postmessage" solution that Nepoxx mentions in the comments of the accepted answer here.
For clarification, here's what worked for me.
Follow steps 1-6 here: https://developers.google.com/identity/sign-in/web/server-side-flow
Install googleapis library npm install --save googleapis
For the server-side token exchange do this:
var googleapis = require('googleapis');
var OAuth2 = googleapis.auth.OAuth2;
var oauth2Client = new OAuth2(
GOOGLE_SSO_CLIENT_ID,
GOOGLE_SSO_CLIENT_SECRET,
'postmessage' // this is where you might otherwise specifiy a redirect_uri
);
oauth2Client.getToken(CODE_FROM_STEP_5_OF_INSTRUCTIONS, function(err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
});
The redirect_uri can be a URL with a custom URL scheme for which the client registered a handler. This is described here: What's a redirect URI? how does it apply to iOS app for OAuth2.0?. It is not so much about "redirecting" it is about a callback endpoint to your app.
And it become really easy if you use VueJS with https://github.com/guruahn/vue-google-oauth2
Client side
import GAuth from 'vue-google-oauth2'
Vue.use(GAuth, {
clientId: 'xxxxxxx.apps.googleusercontent.com',
scope: 'profile',
})
async signWithGoogle() {
const code = await this.$gAuth.getAuthCode() //
console.log(code ) // { code: 'x/xxxxxxxxxx' }
// send the code to your auth server
// and retrieve a JWT or something to keep in localstorage
// to send on every request and compare with database
}
Server side
import { google } from 'googleapis'
const oauth2Client = new google.auth.OAuth2(GOOGLE_ID, GOOGLE_SECRET, 'postmessage')
google.options({ auth: oauth2Client })
async function getAccount(code) {
// the code you sent with the client
const { tokens } = await oauth2Client.getToken(code)
oauth2Client.setCredentials(tokens)
const oauth2 = google.oauth2({ version: 'v2' })
const { data: { id } } = await oauth2.userinfo.get()
// there you have the id of the user to store it in the database
// and send it back in a JWT
}

Resources