W poprzednich częściach omówiliśmy podstawy uwierzytelniania oraz zaimplementowaliśmy prostą autoryzację z użyciem JWT. Teraz zagłębimy się w różne strategie uwierzytelniania, które możesz zastosować w swoich aplikacjach.
Dwa główne podejścia do uwierzytelniania to:
W tym podejściu serwer utrzymuje stan sesji dla każdego zalogowanego użytkownika:
1// Przykład konfiguracji sesji w Express
2import express from 'express';
3import session from 'express-session';
4
5const app = express();
6
7app.use(session({
8 secret: 'tajny-klucz-sesji',
9 resave: false,
10 saveUninitialized: false,
11 cookie: { secure: true, maxAge: 3600000 } // 1 godzina
12}));
13
14app.post('/login', (req, res) => {
15 // Po pomyślnym uwierzytelnieniu
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 // Użytkownik ma ważną sesję
24 res.send('Dane chronione');
25 } else {
26 res.status(401).send('Brak dostępu');
27 }
28});Zalety:
Wady:
W tym podejściu serwer generuje token, który klient przechowuje i przesyła z każdym żądaniem:
1// Przykład implementacji JWT w Express
2import express from 'express';
3import jwt from 'jsonwebtoken';
4
5const app = express();
6const JWT_SECRET = 'tajny-klucz-jwt';
7
8app.post('/login', (req, res) => {
9 // Po pomyślnym uwierzytelnieniu
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// Middleware weryfikujący token
20const verifyToken = (req, res, next) => {
21 const token = req.headers.authorization?.split(' ')[1];
22
23 if (!token) {
24 return res.status(401).send('Brak tokenu autoryzacji');
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('Nieprawidłowy token');
33 }
34};
35
36app.get('/protected', verifyToken, (req, res) => {
37 res.send('Dane chronione');
38});Zalety:
Wady:
OAuth 2.0 to standard protokołu autoryzacji, który umożliwia aplikacjom dostęp do zasobów użytkownika na innym serwerze bez konieczności dzielenia się danymi uwierzytelniającymi.
1// Implementacja OAuth z Google w Express i Passport
2import express from 'express';
3import passport from 'passport';
4import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
5
6const app = express();
7
8// Konfiguracja Passport
9passport.use(new GoogleStrategy({
10 clientID: 'TWOJE_GOOGLE_CLIENT_ID',
11 clientSecret: 'TWOJE_GOOGLE_CLIENT_SECRET',
12 callbackURL: 'http://localhost:3000/auth/google/callback'
13 },
14 function(accessToken, refreshToken, profile, done) {
15 // Znajdź lub utwórz użytkownika w bazie danych
16 User.findOrCreate({ googleId: profile.id }, function (err, user) {
17 return done(err, user);
18 });
19 }
20));
21
22// Trasy uwierzytelniania
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 // Przekierowanie po pomyślnym uwierzytelnieniu
30 res.redirect('/dashboard');
31 }
32);Uwierzytelnianie wieloskładnikowe (Multi-Factor Authentication) zwiększa bezpieczeństwo, wymagając od użytkownika dostarczenia dwóch lub więcej dowodów tożsamości.
1// Implementacja 2FA z TOTP w Node.js
2import express from 'express';
3import speakeasy from 'speakeasy';
4import QRCode from 'qrcode';
5
6const app = express();
7app.use(express.json());
8
9// Generowanie tajnego klucza dla użytkownika
10app.post('/generate-2fa', (req, res) => {
11 const secret = speakeasy.generateSecret({
12 name: 'MojaAplikacja:' + req.user.email
13 });
14
15 // Zapisz secret.base32 w bazie danych dla tego użytkownika
16 saveUserSecret(req.user.id, secret.base32);
17
18 // Generuj kod QR
19 QRCode.toDataURL(secret.otpauth_url, (err, imageUrl) => {
20 if (err) {
21 return res.status(500).send('Błąd generowania kodu QR');
22 }
23
24 res.json({
25 secret: secret.base32,
26 qrCode: imageUrl
27 });
28 });
29});
30
31// Weryfikacja kodu jednorazowego
32app.post('/verify-2fa', (req, res) => {
33 const { token } = req.body;
34
35 // Pobierz secret użytkownika z bazy danych
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 // Włącz 2FA dla użytkownika w bazie danych
46 enableUser2FA(req.user.id);
47 res.json({ success: true });
48 } else {
49 res.status(400).json({ success: false, message: 'Nieprawidłowy kod' });
50 }
51});Single Sign-On to proces uwierzytelniania, który pozwala użytkownikowi na dostęp do wielu aplikacji po jednorazowym zalogowaniu.
1// Przykład implementacji SAML SSO w 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: 'moja-aplikacja',
12 cert: 'CERTYFIKAT_DOSTAWCY_TOŻSAMOŚCI'
13}, (profile, done) => {
14 // Znajdź lub utwórz użytkownika w bazie danych
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);Bezpieczne hasła
Zabezpieczenie tokenów
Transport
Ogólne zasady
Wybór odpowiedniej strategii uwierzytelniania zależy od wielu czynników, takich jak:
Pamiętaj, że bezpieczeństwo uwierzytelniania to proces ciągły, wymagający regularnej aktualizacji i dostosowywania się do nowych zagrożeń i standardów bezpieczeństwa.
W następnych lekcjach przejdziemy do implementacji zaawansowanych mechanizmów autoryzacji i zarządzania uprawnieniami użytkowników.