22
33In this example we will implement security and redirect to login page if is not authorized using a JWT token in Cookies.
44
5- # Steps to build it
5+ ## Install dependencies
66
7- - ` npm install ` to install packages:
7+ ` npm install ` to install packages:
88
99``` bash
10+ cd back
1011npm install
1112```
1213
13- - Start ` back ` app:
14+ ``` bash
15+ cd front
16+ npm install
17+ ```
18+
19+ ## Start apps
20+
21+ Start ` back ` app:
1422
1523``` bash
1624cd ./back
@@ -19,31 +27,47 @@ npm start
1927
2028> NOTE: We added ` .env ` file only for demo purpose. We should ignore this one and add a ` .env.example ` as example.
2129
22- - Let's install ` cookie-parser ` to save token in a cookie (backend project):
30+ Start ` front ` app:
31+
32+ ``` bash
33+ cd ./front
34+ npm start
35+ ```
36+
37+ ## Configure cookies
38+
39+ Let's install ` cookie-parser ` to save token in a cookie (backend project):
2340
2441``` bash
2542npm install cookie-parser --save
2643```
2744
28- - Configure it:
45+ Configure it:
2946
30- _ ./back/src/core/servers/express .server.ts_
47+ _ ./back/src/core/servers/rest-api .server.ts_
3148
3249``` diff
3350import express from 'express';
51+ import cors from 'cors';
3452+ import cookieParser from 'cookie-parser';
3553
36- export const createApp = () => {
54+ export const createRestApiServer = () => {
3755 const app = express();
38-
3956 app.use(express.json());
57+ app.use(
58+ cors({
59+ credentials: true,
60+ origin: '*',
61+ })
62+ );
4063+ app.use(cookieParser());
64+
4165 return app;
4266};
4367
4468```
4569
46- - Remove ` token ` from body:
70+ Remove ` token ` from body:
4771
4872_ ./back/src/pods/security/security.api-model.ts_
4973
@@ -71,7 +95,7 @@ const createUserSession = (user: User): UserSession => {
7195
7296```
7397
74- - Update login method:
98+ Update login method:
7599
76100_ ./back/src/pods/security/security.api.ts_
77101
@@ -87,8 +111,7 @@ _./back/src/pods/security/security.api.ts_
87111 if (currentUser) {
88112 const userSession = createUserSession(currentUser);
89113+ const token = createToken(currentUser);
90-
91- + res.cookie(headerConstants.authorization, token);
114+ + res.cookie(HEADERS.AUTHORIZATION, token);
92115 res.send(userSession);
93116 } else {
94117 res.sendStatus(401);
@@ -97,19 +120,19 @@ _./back/src/pods/security/security.api.ts_
97120
98121```
99122
100- - Add cookie options:
123+ Add cookie options:
101124
102125_ ./back/src/pods/security/security.constants.ts_
103126
104127``` diff
105128+ import { CookieOptions } from 'express';
106- + import { envConstants } from 'core/constants';
129+ + import { ENV } from '# core/constants/index.js ';
107130
108- export const jwtSignAlgorithm = 'HS256';
131+ export const JWT_SIGN_ALGORITHM = 'HS256';
109132
110- + export const cookieOptions : CookieOptions = {
133+ + export const COOKIE_OPTIONS : CookieOptions = {
111134+ httpOnly: true,
112- + secure: envConstants.isProduction ,
135+ + secure: ENV.IS_PRODUCTION ,
113136+ };
114137
115138```
@@ -118,8 +141,8 @@ _./back/src/pods/security/security.api.ts_
118141``` diff
119142...
120143import { jwtMiddleware } from './security.middlewares';
121- - import { jwtSignAlgorithm } from './security.constants';
122- + import { jwtSignAlgorithm, cookieOptions } from './security.constants';
144+ - import { JWT_SIGN_ALGORITHM } from './security.constants.js ';
145+ + import { JWT_SIGN_ALGORITHM, COOKIE_OPTIONS } from './security.constants.js ';
123146...
124147
125148 .post('/login', async (req, res) => {
@@ -131,9 +154,8 @@ import { jwtMiddleware } from './security.middlewares';
131154 if (currentUser) {
132155 const userSession = createUserSession(currentUser);
133156 const token = createToken(currentUser);
134-
135- - res.cookie(headerConstants.authorization, token);
136- + res.cookie(headerConstants.authorization, token, cookieOptions);
157+ - res.cookie(HEADERS.AUTHORIZATION, token);
158+ + res.cookie(HEADERS.AUTHORIZATION, token, COOKIE_OPTIONS);
137159 res.send(userSession);
138160 } else {
139161 res.sendStatus(401);
@@ -142,35 +164,35 @@ import { jwtMiddleware } from './security.middlewares';
142164
143165```
144166
145- - Update security middleware to read cookies:
167+ Update security middleware to read cookies:
146168
147169_ ./back/src/pods/security/security.middlewares.ts_
148170
149171``` diff
150172import { Request } from 'express';
151173import expressJwt from 'express-jwt';
152- import { envConstants, headerConstants } from 'core/constants';
153- import { jwtSignAlgorithm } from './security.constants';
174+ import { ENV, HEADERS } from '# core/constants/index.js ';
175+ import { JWT_SIGN_ALGORITHM } from './security.constants.js ';
154176
155177export const jwtMiddleware = expressJwt({
156- secret: envConstants .TOKEN_AUTH_SECRET,
157- algorithms: [jwtSignAlgorithm ],
178+ secret: ENV .TOKEN_AUTH_SECRET,
179+ algorithms: [JWT_SIGN_ALGORITHM ],
158180 getToken: (req: Request) => {
159181- const tokenWithBearer = req.headers
160182+ const tokenWithBearer = req.cookies
161- - ? (req.headers[headerConstants.authorization ] as string)
162- + ? (req.cookies[headerConstants.authorization ] as string)
183+ - ? (req.headers[HEADERS.AUTHORIZATION ] as string)
184+ + ? (req.cookies[HEADERS.AUTHORIZATION ] as string)
163185 : '';
164186
165- const [, token] = tokenWithBearer.split(`${headerConstants.bearer } `) || [];
187+ const [, token] = tokenWithBearer.split(`${HEADERS.BEARER } `) || [];
166188
167189 return token;
168190 },
169191});
170192
171193```
172194
173- - Implement logout method:
195+ Implement logout method:
174196
175197_ ./back/src/pods/security/security.api.ts_
176198
@@ -181,71 +203,69 @@ _./back/src/pods/security/security.api.ts_
181203 // Different approaches:
182204 // - Short expiration times in token
183205 // - Black list tokens on DB
184- + res.clearCookie(headerConstants.authorization );
206+ + res.clearCookie(HEADERS.AUTHORIZATION );
185207 res.sendStatus(200);
186208 });
187209```
188210
189- - Notice that we don't need to update the front code to run example.
211+ > Try to login and get list using the frontend app.
212+ >
213+ > We need extra configuration to make it works Cookies with CORS.
190214
215+ Restore proxy configuration:
191216
192- ## Cookie expiration
217+ _ ./front/vite.config.ts _
193218
194- Right now, cookie expires when users close the browser. We could add some expiration time:
219+ ``` diff
220+ import { defineConfig } from 'vite';
221+ import react from '@vitejs/plugin-react';
222+
223+ export default defineConfig({
224+ plugins: [react()],
225+ + server: {
226+ + proxy: {
227+ + '/api': 'http://localhost:3000',
228+ + },
229+ + },
230+ });
195231
196- _ ./back/src/pods/security/security.constants.ts _
232+ ```
197233
198- ``` diff
199- import { CookieOptions } from 'express';
200- import { envConstants } from 'core/constants';
234+ _ ./front/src/pods/login/api/login.api.ts_
201235
202- export const jwtSignAlgorithm = 'HS256';
236+ ``` diff
237+ ...
203238
204- - export const cookieOptions: CookieOptions = {
205- - httpOnly: true,
206- - secure: envConstants.isProduction
207- - };
239+ - const url = 'http://localhost:3000/api/security/login';
240+ + const url = '/api/security/login';
208241
209- + export const getCookieOptions = (expires: Date): CookieOptions => ({
210- + httpOnly: true,
211- + secure: envConstants.isProduction,
212- + expires,
213- + });
242+ ...
214243
215244```
216245
217- _ ./back /src/pods/security/security .api.ts_
246+ _ ./front /src/pods/list/api/list .api.ts_
218247
219248``` diff
220249...
221- - import { jwtSignAlgorithm, cookieOptions } from './security.constants';
222- + import { jwtSignAlgorithm, getCookieOptions } from './security.constants';
250+
251+ - const clientUrl = 'http://localhost:3000/api/clients';
252+ + const clientUrl = '/api/clients';
253+ - const orderUrl = 'http://localhost:3000/api/orders';
254+ + const orderUrl = '/api/orders';
223255
224256...
225257
226- .post('/login', async (req, res) => {
227- const { user, password } = req.body;
228- const currentUser = userList.find(
229- (u) => u.userName == user && u.password === password
230- );
258+ ```
231259
232- if (currentUser) {
233- const userSession = createUserSession(currentUser);
234- const token = createToken(currentUser);
235- + const expires = new Date();
236- + expires.setDate(new Date().getDate() + 1); // Add one day
237-
238- res.cookie(
239- headerConstants.authorization,
240- token,
241- - cookieOptions
242- + getCookieOptions(expires)
243- );
244- res.send(userSession);
245- } else {
246- res.sendStatus(401);
247- }
248- })
260+ _ ./front/src/core/app-bar/api/app-bar.api.ts_
261+
262+ ``` diff
263+ ...
264+
265+ - const url = 'http://localhost:3000/api/security/logout';
266+ + const url = '/api/security/logout';
267+
268+ ...
249269
250270```
251271
@@ -257,19 +277,19 @@ _./back/src/pods/security/security.constants.ts_
257277
258278``` diff
259279import { CookieOptions } from 'express';
260- import { envConstants } from 'core/constants';
280+ import { ENV } from '# core/constants/index.js ';
261281
262- export const jwtSignAlgorithm = 'HS256';
282+ export const JWT_SIGN_ALGORITHM = 'HS256';
263283
264- export const cookieOptions : CookieOptions = {
284+ export const COOKIE_OPTIONS : CookieOptions = {
265285- httpOnly: true,
266286+ httpOnly: false,
267- secure: envConstants.isProduction ,
287+ secure: ENV.IS_PRODUCTION ,
268288};
269289
270290```
271291
272- - Now we could write this code in browser console:
292+ Now we could write this code in browser console:
273293
274294```
275295document.cookie
0 commit comments