|
1 | | -# SafePlaces Authentication Service |
| 1 | +# SafePlaces Information Security |
2 | 2 |
|
3 | 3 | [](https://github.com/Path-Check/safeplaces-auth/actions?query=workflow%3A%22Node.js+CI%22) |
4 | | -[](https://coveralls.io/github/Path-Check/safeplaces-auth?branch=master) |
5 | 4 |
|
6 | | -The modular authentication service for the SafePlaces backend. |
| 5 | +The modular information security service for the SafePlaces backend. |
7 | 6 |
|
8 | 7 | ```shell script |
9 | 8 | # Install using NPM |
10 | | -npm install @pathcheck/safeplaces-auth@^1.0.0-alpha.5 |
| 9 | +npm install @pathcheck/safeplaces-auth@^2.0.0-alpha.1 |
11 | 10 |
|
12 | 11 | # Install using Yarn |
13 | | -yarn add @pathcheck/safeplaces-auth@^1.0.0-alpha.5 |
| 12 | +yarn add @pathcheck/safeplaces-auth@^2.0.0-alpha.1 |
14 | 13 | ``` |
15 | 14 |
|
16 | | -# Table of Contents |
17 | | -* [Examples](#examples) |
18 | | - * [Securing API endpoints](#securing-api-endpoints) |
19 | | - * [Handling login requests](#handling-login-requests) |
20 | | - * [Handling logout requests](#handling-logout-requests) |
21 | | -* [Strategies](#strategies) |
22 | | - * [Auth0](#auth0) |
23 | | - * [Symmetric JWT](#symmetric-jwt) |
24 | | - * [Dynamic strategy selection](#dynamic-strategy-selection) |
25 | | -* [Debugging](#debugging) |
| 15 | +## API Specification |
26 | 16 |
|
27 | | -# Examples |
28 | | - |
29 | | -## Securing API endpoints |
30 | | - |
31 | | -The authentication service can be instantiated and attached to an |
32 | | -existing Express application. The point or order at which you attach |
33 | | -the service relative to other middleware affects the point at which |
34 | | -it is invoked. |
35 | | - |
36 | | -```javascript |
37 | | -const auth = require('@pathcheck/safeplaces-auth'); |
38 | | - |
39 | | -// Instantiate a public key retrieval client. |
40 | | -const pkClient = new auth.JWKSClient( |
41 | | - // Pass the URL for the JWKS. |
42 | | - `${process.env.AUTH0_BASE_URL}/.well-known/jwks.json`, |
43 | | -); |
44 | | - |
45 | | -// Instantiate a request verification strategy. |
46 | | -const auth0Strategy = new auth.strategies.Auth0({ |
47 | | - jwksClient: pkClient, |
48 | | - // The API audience is checked against the user's access token. |
49 | | - apiAudience: process.env.AUTH0_API_AUDIENCE, |
50 | | -}); |
51 | | - |
52 | | -// Instantiate a strategy enforcer. |
53 | | -const enforcer = new auth.Enforcer({ |
54 | | - strategy: auth0Strategy, |
55 | | - // Pass in an asynchronous database lookup function |
56 | | - // that takes the user ID as the only argument. |
57 | | - userGetter: id => User.findOne(id), |
58 | | -}); |
59 | | - |
60 | | -// `app` is your Express application. |
61 | | -// A middleware is attached to the app, and it intercepts every |
62 | | -// request for verification. The argument to `enforcer.secure` |
63 | | -// affects which routes are secured. |
64 | | -enforcer.secure(app); |
65 | | -``` |
66 | | - |
67 | | -For greater flexibility, you access the built-in `enforcer.handleRequest(req, res)` |
68 | | -function to selectively handle requests under your own logic. Note that `enforcer` |
69 | | -will continue to end the request with `403 Forbidden` if the request is unauthorized. |
70 | | - |
71 | | -```javascript |
72 | | -app.use((req, res, next) => { |
73 | | - if (req.headers['X-Bypass-Login']) { |
74 | | - return next(); |
75 | | - } else { |
76 | | - return enforcer |
77 | | - // Enforcer ends the request with a `403 Forbidden` if it is unauthorized, |
78 | | - // meaning `next` will not be called unless the request is authorized. |
79 | | - .handleRequest(req, res, next) |
80 | | - .catch(err => next(err)); |
81 | | - } |
82 | | -}); |
83 | | -``` |
84 | | - |
85 | | -For the most flexibility, you can use `enforcer.processRequest(req)` to only validate a request, |
86 | | -allowing you to decide how to handle the validation result yourself, whether that be ending |
87 | | -the request or ignoring the error. |
88 | | - |
89 | | -```javascript |
90 | | -app.use((req, res, next) => { |
91 | | - enforcer |
92 | | - .processRequest(req) |
93 | | - // `.then` is called only if `processRequest` has |
94 | | - // determined the request to be authorized. |
95 | | - .then(note => { |
96 | | - // `note` is an error encountered by `processRequest` that may be indicated in server logs |
97 | | - // or in a request header. It was not enough to halt the request, but the server |
98 | | - // may encounter an unexpected error if logic continues. |
99 | | - console.log(note); |
100 | | - next(); |
101 | | - }) |
102 | | - // Otherwise, an error describing the validation error |
103 | | - // will be thrown, and you can decide what to do with it. |
104 | | - .catch(err => res.status(403).send('Forbidden')); |
105 | | -}); |
106 | | -``` |
107 | | - |
108 | | -## Handling login requests |
109 | | - |
110 | | -```javascript |
111 | | -// Instantiate a login handler. |
112 | | -const loginHandler = new auth.handlers.Login({ |
113 | | - auth0: { |
114 | | - baseUrl: process.env.AUTH0_BASE_URL, |
115 | | - apiAudience: process.env.AUTH0_API_AUDIENCE, |
116 | | - clientId: process.env.AUTH0_CLIENT_ID, |
117 | | - clientSecret: process.env.AUTH0_CLIENT_SECRET, |
118 | | - realm: process.env.AUTH0_REALM, |
119 | | - }, |
120 | | - cookie: { |
121 | | - // Enable/disable cookie attributes depending on environment. |
122 | | - secure: process.env.NODE_ENV !== 'development', |
123 | | - sameSite: process.env.BYPASS_SAME_SITE !== 'true', |
124 | | - }, |
125 | | -}); |
126 | | - |
127 | | -// Handle all requests to the login endpoint. |
128 | | -// Since we are passing around the `handle` function, make sure |
129 | | -// to bind the handle function to its object. |
130 | | -app.post('/auth/login', loginHandler.handle.bind(loginHandler)); |
131 | | -``` |
132 | | - |
133 | | -## Handling logout requests |
134 | | - |
135 | | -```javascript |
136 | | -// Instantiate a logout handler. |
137 | | -const logoutHandler = new auth.handlers.Logout({ |
138 | | - redirect: 'https://example.com/logout-success/', |
139 | | - cookie: { |
140 | | - // Enable/disable cookie attributes depending on environment. |
141 | | - secure: process.env.NODE_ENV !== 'development', |
142 | | - sameSite: process.env.BYPASS_SAME_SITE !== 'true', |
143 | | - }, |
144 | | -}); |
145 | | - |
146 | | -// Handle all requests to the logout endpoint. |
147 | | -// Since we are passing around the `handle` function, make sure |
148 | | -// to bind the handle function to its object. |
149 | | -app.get('/auth/logout', logoutHandler.handle.bind(logoutHandler)); |
150 | | -``` |
151 | | - |
152 | | -# Strategies |
153 | | - |
154 | | -**Supported strategies:** |
155 | | -- Auth0 asymmetric JWT |
156 | | -- Symmetric JWT |
157 | | - |
158 | | -## Auth0 |
159 | | - |
160 | | -Validate the JSON Web Token by checking the signature with |
161 | | -the retrieved public key, and validate the API audience. |
162 | | - |
163 | | -```javascript |
164 | | -const auth = require('@pathcheck/safeplaces-auth'); |
165 | | - |
166 | | -const pkClient = new auth.JWKSClient( |
167 | | - `${process.env.AUTH0_BASE_URL}/.well-known/jwks.json`, |
168 | | -); |
169 | | -const auth0Strategy = new auth.strategies.Auth0({ |
170 | | - jwksClient: pkClient, |
171 | | - apiAudience: process.env.AUTH0_API_AUDIENCE, |
172 | | -}); |
173 | | -``` |
174 | | - |
175 | | -## Symmetric JWT |
176 | | - |
177 | | -Validate the JSON Web Token by checking the signature with |
178 | | -a fixed private key. |
179 | | - |
180 | | -```javascript |
181 | | -const auth = require('@pathcheck/safeplaces-auth'); |
182 | | - |
183 | | -const symJWTStrategy = new auth.strategies.SymJWT({ |
184 | | - algorithm: 'HS256', |
185 | | - privateKey: process.env.JWT_SECRET, |
186 | | -}); |
187 | | -``` |
188 | | - |
189 | | -## Dynamic strategy selection |
190 | | - |
191 | | -You can also pass a function into the strategy parameter |
192 | | -to dynamically select the strategy based on the request |
193 | | -or some other variables. |
194 | | - |
195 | | -The function should accept the request as the only argument |
196 | | -and return the desired strategy or a promise resolving the |
197 | | -desired strategy. |
198 | | - |
199 | | -```javascript |
200 | | -const enforcer = new auth.Enforcer({ |
201 | | - strategy: req => { |
202 | | - console.log(req); |
203 | | - // Check something in the request. |
204 | | - // ... |
205 | | - // Example conditional: |
206 | | - if (process.env.NODE_ENV === 'development') { |
207 | | - return symJWTStrategy; |
208 | | - } else { |
209 | | - return auth0Strategy; |
210 | | - } |
211 | | - }, |
212 | | -}); |
213 | | -``` |
214 | | - |
215 | | -# Debugging |
216 | | - |
217 | | -To debug why the `enforcer` is rejecting a request, set the |
218 | | -`AUTH_LOGGING` environment variable to `verbose`, and the request |
219 | | -validation error will be logged every time. |
| 17 | +See [SafePlaces Information Security API](docs/api/README.md) for more information. |
0 commit comments