Read more here.
Original README archived below.
This component allows creating a session management API using Google Cloud HTTP Functions.
It relies on the following dependencies:
- @google-cloud/datastore - for reading user data from Google Datastore.
- Bcrypt - for verifying passwords.
- client-sessions - for session cookie management.
Create a new Google Cloud Function:
-
The name of your Google Cloud Function will also be the endpoint name. So, if you name your function "session", the endpoint served will also be called "session". You can name it anything you like.
-
Select HTTP Trigger.
-
In the inline editor, paste the following code:
const SessionsApi = require('@gfa/sessions-ds')
const api = new SessionsApi()
exports.handleRequest = function (req, res) {
api.handle(req, res)
}
-
Click package.json tab.
-
Paste the following code (ajust name and version to your liking):
{
"name": "your-function",
"version": "0.0.1",
"dependencies": {
"@gfa/sessions-ds": "github:pauloddr/gfa-sessions-ds"
}
}
-
Set the Entry Point to
handleRequest
. If you want to use another name, make sure to adjust it in your function code (Step 3). -
Click More to show Advanced Options.
-
Create an environment variable called
GFA_SESSION_SECRET
containing the secret used to generate the encrypted session cookie. Without this, the function won't start. -
Click Create to deploy the function.
After the deploy is finished, the following endpoints will be served (assuming you named your function "session" in step 1):
POST /session
- creates a new session (user sign-in)GET /session
- returns information about current sessionDELETE /session
- destroys session (user sign-out)OPTIONS /session
- returns headers
This package reads user data from Google Datastore. As such, Datastore must be enabled in the Google Project, and user data should already exist.
Refer to Configuration to determine which Datastore Kind and properties will be used for fetching and verifying user data.
This section details the endpoints served by the function.
A function name of "session" is used in examples. If your function has a different name, endpoints will have that name instead.
POST /session
This endpoint creates a new session if provided username/password are valid.
Successful responses set a cookie in the browser (or client) with the session credentials.
Request Body | Response |
---|---|
// valid credentials
{
"username": "MyUsername",
"password": "abc123"
} |
// statusCode: 201 Created
{
"id": "12345",
"username": "MyUsername"
} |
// non-existing username or wrong password
{
"username": "MyUsername",
"password": "wrongpass"
} |
// statusCode: 401 Unauthorized
// empty response |
Please note that if you use custom field names, you must use those modified names in the request body as well. All responses will also present the custom names.
Example for custom fields named user
and pass
:
Request Body | Response |
---|---|
// valid credentials
{
"user": "MyUsername",
"pass": "abc123"
} |
// statusCode: 201 Created
{
"id": "12345",
"user": "MyUsername"
} |
GET /session
Reads the session cookie and responds with friendly data.
Context | Response |
---|---|
Session cookie present |
// statusCode: 200 OK
{
"id": "12345",
"username": "MyUsername"
} |
Session cookie not present |
// statusCode: 401 Unauthorized
// empty response |
To add more user fields to the output of a successful response, set session.expose
with an array of field names. It defaults to the user id
only.
Setting this option will overwrite the defaults. The example below will remove id
from responses, keep username
, and add createdAt
:
const api = new SessionsApi({
session: {expose: ['username', 'createdAt']}
})
DELETE /session
Signs a user out, removing the session cookie from the browser/client.
Request Body | Response |
---|---|
(empty) |
// statusCode 204 No Content
// empty response |
OPTIONS /session
Some clients will fire a "preflight request" prior to making the real request to verify CORS options and other security headers.
Request Body | Response |
---|---|
(empty) |
// statusCode 204 No Content
// empty response with CORS and other security headers |
Settings can be set upon creating an instance. See defaults below.
var api = new SessionsApi({
// Session management options
session: {
// REQUIRED! if not present, function will intentionally crash
secret: process.env['GFA_SESSION_SECRET'],
// name of the session cookie
name: 'userSession',
// session expiration in ms
duration: 24 * 60 * 60 * 1000,
// session active duration in ms
activeDuration: 1000 * 60 * 5,
// array of user fields to expose in session object
// id is already included
expose: []
},
// Datastore "Kind" where user data is stored
table: 'User',
// Datastore namespace
namespace: null,
fields: {
// name of the field that is used together with password during sign-in
primary: 'username',
// name of the field that stores the password
password: 'password'
},
// array of headers that are sent in all responses
// CORS and security headers should be included here, if your browser/client needs them
headers: []
})
-
Request the
Session
object from this library in your other Google Functions (i.e., API endpoints that require session credentials) and create an instance with the same options defined in thesession
section of the main session function; -
define environment variable
GFA_SESSION_SECRET
in your new function with the same value as the main session function; -
call
authorize()
on thesession
instance to validate it:
// Note the different class name and require path
const Session = require('@gfa/sessions-ds/session')
// Create and configure this object with the same options
// as the "session" section of your /sessions function
const session = new Session({/*...*/})
// Entry Point
exports.handleRequest = function (req, res) {
session.authorize(req, res, mainFunction)
}
function mainFunction (err, req, res, user) {
if (err) {
if (err.message === 'UNAUTHORIZED') {
return res.status(401).end()
}
// handle other errors
return res.status(500).end()
}
// rest of your code
// `user` parameter contains exposed session data
}
MIT