In previous parts, we discussed the basics of authentication and implemented simple authorization using JWT. Now we will dive into different authentication strategies you can apply in your applications.
The two main approaches to authentication are:
In this approach, the server maintains session state for each logged-in user:
1// Example session configuration in Express
2import express from 'express';
3import session from 'express-session';
4
5const app = express();
6
7app.use(session({
8 secret: 'secret-session-key',
9 resave: false,
10 saveUninitialized: false,
11 cookie: { secure: true, maxAge: 3600000 } // 1 hour
12}));
13
14app.post('/login', (req, res) => {
15 // After successful authentication
16 req.session.authenticated = true;
17 req.session.userId = user.id;
18 res.send({ success: true });
19});
20
21app.get('/protected', (req, res) => {
22 if (req.session.authenticated) {
23 // User has a valid session
24 res.send('Protected data');
25 } else {
26 res.status(401).send('Access denied');
27 }
28});Advantages:
Disadvantages:
In this approach, the server generates a token that the client stores and sends with each request:
1// Example JWT implementation in Express
2import express from 'express';
3import jwt from 'jsonwebtoken';
4
5const app = express();
6const JWT_SECRET = 'secret-jwt-key';
7
8app.post('/login', (req, res) => {
9 // After successful authentication
10 const token = jwt.sign(
11 { userId: user.id, email: user.email },
12 JWT_SECRET,
13 { expiresIn: '1h' }
14 );
15
16 res.send({ token });
17});
18
19// Token verification middleware
20const verifyToken = (req, res, next) => {
21 const token = req.headers.authorization?.split(' ')[1];
22
23 if (!token) {
24 return res.status(401).send('Missing authorization token');
25 }
26
27 try {
28 const decoded = jwt.verify(token, JWT_SECRET);
29 req.user = decoded;
30 next();
31 } catch (error) {
32 return res.status(401).send('Invalid token');
33 }
34};
35
36app.get('/protected', verifyToken, (req, res) => {
37 res.send('Protected data');
38});Advantages:
Disadvantages:
OAuth 2.0 is an authorization protocol standard that allows applications to access user resources on another server without sharing credentials.
1// OAuth implementation with Google in Express and Passport
2import express from 'express';
3import passport from 'passport';
4import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
5
6const app = express();
7
8// Passport configuration
9passport.use(new GoogleStrategy({
10 clientID: 'YOUR_GOOGLE_CLIENT_ID',
11 clientSecret: 'YOUR_GOOGLE_CLIENT_SECRET',
12 callbackURL: 'http://localhost:3000/auth/google/callback'
13 },
14 function(accessToken, refreshToken, profile, done) {
15 // Find or create the user in the database
16 User.findOrCreate({ googleId: profile.id }, function (err, user) {
17 return done(err, user);
18 });
19 }
20));
21
22// Authentication routes
23app.get('/auth/google',
24 passport.authenticate('google', { scope: ['profile', 'email'] }));
25
26app.get('/auth/google/callback',
27 passport.authenticate('google', { failureRedirect: '/login' }),
28 (req, res) => {
29 // Redirect after successful authentication
30 res.redirect('/dashboard');
31 }
32);Multi-Factor Authentication (MFA) increases security by requiring the user to provide two or more proofs of identity.
1// 2FA implementation with TOTP in Node.js
2import express from 'express';
3import speakeasy from 'speakeasy';
4import QRCode from 'qrcode';
5
6const app = express();
7app.use(express.json());
8
9// Generating a secret key for the user
10app.post('/generate-2fa', (req, res) => {
11 const secret = speakeasy.generateSecret({
12 name: 'MyApp:' + req.user.email
13 });
14
15 // Save secret.base32 in the database for this user
16 saveUserSecret(req.user.id, secret.base32);
17
18 // Generate the QR code
19 QRCode.toDataURL(secret.otpauth_url, (err, imageUrl) => {
20 if (err) {
21 return res.status(500).send('QR code generation error');
22 }
23
24 res.json({
25 secret: secret.base32,
26 qrCode: imageUrl
27 });
28 });
29});
30
31// One-time code verification
32app.post('/verify-2fa', (req, res) => {
33 const { token } = req.body;
34
35 // Fetch the user's secret from the database
36 const userSecret = getUserSecret(req.user.id);
37
38 const verified = speakeasy.totp.verify({
39 secret: userSecret,
40 encoding: 'base32',
41 token: token
42 });
43
44 if (verified) {
45 // Enable 2FA for the user in the database
46 enableUser2FA(req.user.id);
47 res.json({ success: true });
48 } else {
49 res.status(400).json({ success: false, message: 'Invalid code' });
50 }
51});Single Sign-On is an authentication process that allows a user to access multiple applications after a single login.
1// Example SAML SSO implementation in Express
2import express from 'express';
3import passport from 'passport';
4import { Strategy as SAMLStrategy } from 'passport-saml';
5
6const app = express();
7
8passport.use(new SAMLStrategy({
9 path: '/login/callback',
10 entryPoint: 'https://sso.example.com/saml2/idp/SSOService.php',
11 issuer: 'my-application',
12 cert: 'IDENTITY_PROVIDER_CERTIFICATE'
13}, (profile, done) => {
14 // Find or create the user in the database
15 return done(null, {
16 id: profile.nameID,
17 email: profile.email,
18 name: profile.displayName
19 });
20}));
21
22app.get('/login',
23 passport.authenticate('saml', { failureRedirect: '/login/fail' }),
24 (req, res) => res.redirect('/')
25);
26
27app.post('/login/callback',
28 passport.authenticate('saml', { failureRedirect: '/login/fail' }),
29 (req, res) => res.redirect('/')
30);Secure passwords
Token security
Transport
General rules
Choosing the right authentication strategy depends on many factors, such as:
Remember that authentication security is an ongoing process requiring regular updates and adaptation to new threats and security standards.
In the next lessons, we will move on to implementing advanced authorization mechanisms and user permission management.