├── .gitignore ├── README.md ├── authentication.js ├── index.js ├── lambda-auth-prod-swagger-integrations,authorizers,documentation.json ├── login.html ├── package.json ├── render.js ├── session.js ├── user.html └── users.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | Archive.zip 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lambda-session-auth 2 | ======== 3 | 4 | Cookie-based session authentication using AWS Lambda and Node. 5 | 6 | Refer to https://www.sandersdenardi.com/aws-lambda-api-auth/ for a detailed explanation. 7 | -------------------------------------------------------------------------------- /authentication.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const users = require('./users'); 4 | 5 | module.exports = { 6 | auth: (username, pass) => { 7 | if (!username || !pass) { 8 | return { success: false, message: 'Must provide username and password.' }; 9 | } else if (!users[username]) { 10 | return { success: false, message: 'User doesn\'t exist.' }; 11 | } else if (users[username].pass !== pass) { 12 | return { success: false, message: 'Incorrect password.' }; 13 | } else { 14 | return { success: true, user: users[username] }; 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const session = require('./session'); 4 | const render = require('./render'); 5 | const authentication = require('./authentication'); 6 | 7 | module.exports = { 8 | get: (event, context) => { 9 | const sess = session.getSession(event.headers); 10 | if (sess.valid) { 11 | render.user({ 12 | username: sess.user.username, 13 | first: sess.user.first, 14 | last: sess.user.last 15 | }, (err, res) => { 16 | if (err) { return context.done(err); } 17 | return context.done(null, res); 18 | }); 19 | } else { 20 | render.login((err, res) => { 21 | if (err) { return context.done(err); } 22 | return context.done(null, res); 23 | }); 24 | } 25 | }, 26 | login: (event, context) => { 27 | const username = event.data.username; 28 | const pass = event.data.password; 29 | const authRes = authentication.auth(username, pass); 30 | if (authRes.success) { 31 | const sess = session.setSession(authRes.user); 32 | return context.done(null, { 33 | success: authRes.success, 34 | Cookie: sess.Cookie 35 | }); 36 | } else { 37 | return context.done(null, authRes) 38 | } 39 | }, 40 | logout: (event, context) => { 41 | const sessionRes = session.getSession(event.headers); 42 | const sess = session.destroySession(sessionRes.user); 43 | context.done(null, { Cookie: sess.Cookie }); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /lambda-auth-prod-swagger-integrations,authorizers,documentation.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "2016-12-01T03:21:28Z", 5 | "title": "lambda-auth" 6 | }, 7 | "schemes": [ 8 | "https" 9 | ], 10 | "paths": { 11 | "/": { 12 | "get": { 13 | "consumes": [ 14 | "application/json" 15 | ], 16 | "produces": [ 17 | "application/json", 18 | "text/html" 19 | ], 20 | "responses": { 21 | "200": { 22 | "description": "200 response", 23 | "schema": { 24 | "$ref": "#/definitions/Empty" 25 | }, 26 | "headers": { 27 | "Content-Type": { 28 | "type": "string" 29 | } 30 | } 31 | } 32 | }, 33 | "x-amazon-apigateway-integration": { 34 | "responses": { 35 | "default": { 36 | "statusCode": "200", 37 | "responseParameters": { 38 | "method.response.header.Content-Type": "'text/html'" 39 | }, 40 | "responseTemplates": { 41 | "text/html": "#set($inputRoot = $input.path('$')) \n$inputRoot" 42 | } 43 | } 44 | }, 45 | "requestTemplates": { 46 | "application/json": "{\n \"headers\": {\n #foreach($header in $input.params().header.keySet())\n \"$header\": \"$util.escapeJavaScript($input.params().header.get($header))\" #if($foreach.hasNext),#end\n #end\n }\n}" 47 | }, 48 | "uri": "", 49 | "passthroughBehavior": "when_no_templates", 50 | "httpMethod": "POST", 51 | "contentHandling": "CONVERT_TO_TEXT", 52 | "type": "aws" 53 | } 54 | } 55 | }, 56 | "/login": { 57 | "post": { 58 | "consumes": [ 59 | "application/json" 60 | ], 61 | "produces": [ 62 | "application/json" 63 | ], 64 | "responses": { 65 | "200": { 66 | "description": "200 response", 67 | "schema": { 68 | "$ref": "#/definitions/Empty" 69 | }, 70 | "headers": { 71 | "Set-Cookie": { 72 | "type": "string" 73 | } 74 | } 75 | } 76 | }, 77 | "x-amazon-apigateway-integration": { 78 | "responses": { 79 | "default": { 80 | "statusCode": "200", 81 | "responseParameters": { 82 | "method.response.header.Set-Cookie": "integration.response.body.Cookie" 83 | } 84 | } 85 | }, 86 | "requestTemplates": { 87 | "application/json": "{\n \"data\": $input.body\n}" 88 | }, 89 | "uri": "", 90 | "passthroughBehavior": "when_no_templates", 91 | "httpMethod": "POST", 92 | "contentHandling": "CONVERT_TO_TEXT", 93 | "type": "aws" 94 | } 95 | } 96 | }, 97 | "/logout": { 98 | "post": { 99 | "consumes": [ 100 | "application/json" 101 | ], 102 | "produces": [ 103 | "application/json" 104 | ], 105 | "responses": { 106 | "200": { 107 | "description": "200 response", 108 | "schema": { 109 | "$ref": "#/definitions/Empty" 110 | }, 111 | "headers": { 112 | "Set-Cookie": { 113 | "type": "string" 114 | } 115 | } 116 | } 117 | }, 118 | "x-amazon-apigateway-integration": { 119 | "responses": { 120 | "default": { 121 | "statusCode": "200", 122 | "responseParameters": { 123 | "method.response.header.Set-Cookie": "integration.response.body.Cookie" 124 | } 125 | } 126 | }, 127 | "requestTemplates": { 128 | "application/json": "{\n \"headers\": {\n #foreach($header in $input.params().header.keySet())\n \"$header\": \"$util.escapeJavaScript($input.params().header.get($header))\" #if($foreach.hasNext),#end\n #end\n }\n}" 129 | }, 130 | "uri": "", 131 | "passthroughBehavior": "when_no_templates", 132 | "httpMethod": "POST", 133 | "contentHandling": "CONVERT_TO_TEXT", 134 | "type": "aws" 135 | } 136 | } 137 | } 138 | }, 139 | "definitions": { 140 | "Empty": { 141 | "type": "object", 142 | "title": "Empty Schema" 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Login 5 | 6 | 7 |

Login

8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-session-auth", 3 | "private": true, 4 | "repository": { 5 | "type": "git", 6 | "url": "git+https://github.com/sedenardi/lambda-session-auth.git" 7 | }, 8 | "author": "Sanders DeNardi", 9 | "homepage": "https://github.com/sedenardi/lambda-session-auth#readme", 10 | "dependencies": { 11 | "cookie": "^0.3.1", 12 | "lodash.template": "^4.4.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /render.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const template = require('lodash.template'); 6 | 7 | const loginPath = path.resolve(__dirname, './login.html'); 8 | const userPath = path.resolve(__dirname, './user.html'); 9 | 10 | module.exports = { 11 | login: (cb) => { 12 | fs.readFile(loginPath, (err, res) => { 13 | if (err) { return cb(err); } 14 | return cb(null, res.toString()); 15 | }); 16 | }, 17 | user: (opts, cb) => { 18 | fs.readFile(userPath, (err, res) => { 19 | if (err) { return cb(err); } 20 | const compiled = template(res.toString()); 21 | const body = compiled(opts); 22 | return cb(null, body); 23 | }); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /session.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const cookie = require('cookie'); 4 | const users = require('./users'); 5 | 6 | const cookieKey = 'SID'; 7 | const cookiePrefix = 'Session::'; 8 | 9 | module.exports = { 10 | getSession: (headers) => { 11 | const cookieStr = headers ? (headers.Cookie || '') : ''; 12 | const cookies = cookie.parse(cookieStr); 13 | if (!cookies[cookieKey]) { 14 | return { valid: false }; 15 | } 16 | const username = cookies[cookieKey].replace(cookiePrefix, ''); 17 | const user = users[username]; 18 | return { 19 | valid: !!user, 20 | user: user 21 | }; 22 | }, 23 | setSession: (user) => { 24 | const sessionId = `${cookiePrefix}${user.username}`; 25 | const newCookie = cookie.serialize(cookieKey, sessionId); 26 | return { Cookie: newCookie }; 27 | }, 28 | destroySession: (user) => { 29 | const clearCookie = cookie.serialize(cookieKey, 'empty', { maxAge: 0 }); 30 | return { Cookie: clearCookie }; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | User 5 | 6 | 7 |

Logged In

8 |
9 | Username: 10 | <%= username %> 11 |
12 |
13 | First Name: 14 | <%= first %> 15 |
16 |
17 | Last Name: 18 | <%= last %> 19 |
20 |
21 | 22 |
23 |
24 | 25 |
26 | <% if (true) { %> 27 | 46 | <% } %> 47 | 48 | 49 | -------------------------------------------------------------------------------- /users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | john: { 5 | username: 'john', 6 | first: 'John', 7 | last: 'Smith', 8 | pass: 'johnpass' 9 | }, 10 | jane: { 11 | username: 'jane', 12 | first: 'Jane', 13 | last: 'Doe', 14 | pass: 'janepass' 15 | } 16 | }; 17 | --------------------------------------------------------------------------------