Creo applicazioni web moderne e strumenti digitali personalizzati per aiutare le attività a crescere attraverso l'innovazione tecnologica. La mia passione è unire informatica ed economia per generare valore reale.
La mia passione per l'informatica è nata tra i banchi dell'Istituto Tecnico Commerciale di Maglie, dove ho scoperto il potere della programmazione e il fascino di creare soluzioni digitali. Fin da subito, ho capito che l'informatica non era solo codice, ma uno strumento straordinario per trasformare idee in realtà.
Durante gli studi superiori in Sistemi Informativi Aziendali, ho iniziato a intrecciare informatica ed economia, comprendendo come la tecnologia possa essere il motore della crescita per qualsiasi attività. Questa visione mi ha accompagnato all'Università degli Studi di Bari, dove ho conseguito la Laurea in Informatica, approfondendo le mie competenze tecniche e la mia passione per lo sviluppo software.
Oggi metto questa esperienza al servizio di imprese, professionisti e startup, creando soluzioni digitali su misura che automatizzano processi, ottimizzano risorse e aprono nuove opportunità di business. Perché la vera innovazione inizia quando la tecnologia incontra le esigenze reali delle persone.
Le Mie Competenze
Analisi Dati & Modelli Previsionali
Trasformo i dati in insights strategici con analisi approfondite e modelli predittivi per decisioni informate
Automazione Processi
Creo strumenti personalizzati che automatizzano operazioni ripetitive e liberano tempo per attività a valore aggiunto
Sistemi Custom
Sviluppo sistemi software su misura, dalle integrazioni tra piattaforme alle dashboard personalizzate
Credo fermamente che l'informatica sia lo strumento più potente per trasformare le idee in realtà e migliorare la vita delle persone.
🚀
Democratizzare la Tecnologia
La mia missione è rendere l'informatica accessibile a tutti: dalle piccole imprese locali alle startup innovative, fino ai professionisti che vogliono digitalizzare la propria attività. Ogni realtà merita di sfruttare le potenzialità del digitale.
💡
Unire Informatica ed Economia
Non è solo questione di scrivere codice: è capire come la tecnologia possa generare valore reale. Intrecciando competenze informatiche e visione economica, aiuto le attività a crescere, ottimizzare processi e raggiungere nuovi traguardi di efficienza e redditività.
🎯
Creare Soluzioni su Misura
Ogni attività è unica, e così devono esserlo le soluzioni. Sviluppo strumenti personalizzati che rispondono alle esigenze specifiche di ciascun cliente, automatizzando processi ripetitivi e liberando tempo per ciò che conta davvero: far crescere il business.
Trasforma la Tua Attività con la Tecnologia
Che tu gestisca un negozio, uno studio professionale o un'azienda, posso aiutarti a sfruttare le potenzialità dell'informatica per lavorare meglio, più velocemente e in modo più intelligente.
Il mio percorso accademico e le tecnologie che padroneggio
Certificazioni Professionali
8 certificazioni conseguite
Nuovo
Visualizza
Reinvention With Agentic AI Learning Program
Anthropic
Dicembre 2024
Nuovo
Visualizza
Agentic AI Fluency
Anthropic
Dicembre 2024
Nuovo
Visualizza
AI Fluency for Students
Anthropic
Dicembre 2024
Nuovo
Visualizza
AI Fluency: Framework and Foundations
Anthropic
Dicembre 2024
Nuovo
Visualizza
Claude with the Anthropic API
Anthropic
Dicembre 2024
Visualizza
Master SQL
RoadMap.sh
Novembre 2024
Visualizza
Oracle Certified Foundations Associate
Oracle
Ottobre 2024
Visualizza
People Leadership Credential
Connect
Settembre 2024
💻 Linguaggi & Tecnologie
☕Java
🐍Python
📜JavaScript
🅰️Angular
⚛️React
🔷TypeScript
🗄️SQL
🐘PHP
🎨CSS/SCSS
🔧Node.js
🐳Docker
🌿Git
💼
12/2024 - Presente
Custom Software Engineering Analyst
Accenture
Bari, Puglia, Italia · Ibrida
Analisi e sviluppo di sistemi informatici attraverso l'utilizzo di Java e Quarkus in Health and Public Sector. Formazione continua su tecnologie moderne per la creazione di soluzioni software personalizzate ed efficienti e sugli agenti.
💼
06/2022 - 12/2024
Analista software e Back End Developer Associate Consultant
Links Management and Technology SpA
Esperienza nell'analisi di sistemi software as-is e flussi ETL utilizzando PowerCenter. Formazione completata su Spring Boot per lo sviluppo di applicazioni backend moderne e scalabili. Sviluppatore Backend specializzato in Spring Boot, con esperienza in progettazione di database, analisi, sviluppo e testing dei task assegnati.
💼
02/2021 - 10/2021
Programmatore software
Adesso.it (prima era WebScience srl)
Esperienza nell'analisi AS-IS e TO-BE, evoluzioni SEO ed evoluzioni website per migliorare le performance e l'engagement degli utenti.
🎓
2018 - 2025
Laurea in Informatica
Università degli Studi di Bari Aldo Moro
Bachelor's degree in Computer Science, focusing on software engineering, algorithms, and modern development practices.
📚
2013 - 2018
Diploma - Sistemi Informativi Aziendali
Istituto Tecnico Commerciale di Maglie
Technical diploma specializing in Business Information Systems, combining IT knowledge with business management.
Contattami
Hai un progetto in mente? Parliamone! Compila il form qui sotto e ti risponderò al più presto.
* Campi obbligatori. I tuoi dati saranno utilizzati solo per rispondere alla tua richiesta.
04 - Secure Authentication: Session Management, Cookies and Modern Identity
Authentication is the front door of your application. If it is vulnerable, every other security
measure can be bypassed. According to the 2024 Verizon DBIR, 74% of data breaches involved
compromised credentials or identity-related attacks. Yet most applications still implement
authentication patterns from 2010: server-side sessions with unprotected cookies, no MFA,
and JWT tokens stored in localStorage.
The authentication landscape in 2025 has changed radically. OAuth 2.1 has consolidated security
best practices by making PKCE mandatory for all clients. Passkeys based on WebAuthn are replacing
traditional passwords, with Apple, Google and Microsoft promoting them as the default standard.
NIST SP 800-63B revised its password guidelines, eliminating mandatory rotation requirements.
This article guides you through the entire authentication attack surface with practical code,
common pitfalls and a security checklist for Node.js applications.
What You Will Learn
Secure session management: HttpOnly, Secure, SameSite cookies and session fixation prevention
JWT best practices and the 5 fatal mistakes that make tokens insecure
OAuth 2.1 with PKCE: authorization code flow for SPAs and mobile apps
WebAuthn and passkeys: practical implementation with SimpleWebAuthn
MFA with TOTP: integration with Google Authenticator and Authy via otpauth
RBAC, ABAC and ReBAC: authorization models for modern applications
Complete Express.js authentication middleware with rate limiting and audit logging
OWASP A07:2021 checklist (Identification and Authentication Failures)
Session Management: The Foundation
Session management is the mechanism by which the server "remembers" an authenticated user across
stateless HTTP requests. A secure session requires four fundamental properties:
unpredictable identifiers, secure transmission,
controlled expiration and correct invalidation on logout.
The primary attack vector is session fixation: the attacker provides the victim
with a known session ID before login, then reuses that same ID after authentication to impersonate
the user. The countermeasure is to always regenerate the session ID after login. A second vector
is session hijacking via XSS, mitigated by cookies with the HttpOnly
flag. A third vector is orphaned sessions: sessions never invalidated server-side
that remain active even after the client deletes the cookie.
// Secure session management with express-session
// npm install express-session connect-pg-simple @types/express-session
import session from 'express-session';
import pgSession from 'connect-pg-simple';
import crypto from 'crypto';
const PgSession = pgSession(session);
app.use(session({
// PostgreSQL store instead of MemoryStore (never use in production)
store: new PgSession({
pool: dbPool,
tableName: 'user_sessions',
createTableIfMissing: true,
}),
// Strong secret from environment variable, never hardcoded
secret: process.env.SESSION_SECRET!, // min 32 random characters
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // Blocks JavaScript access (XSS protection)
secure: true, // HTTPS only
sameSite: 'strict', // Blocks cross-origin CSRF
maxAge: 8 * 60 * 60 * 1000, // 8 hours in milliseconds
domain: process.env.COOKIE_DOMAIN,
path: '/',
},
// Custom name instead of 'connect.sid' (reduces fingerprinting)
name: '__session',
genid: () => crypto.randomBytes(32).toString('hex'),
}));
// CRITICAL: Regenerate session ID after login (prevents session fixation)
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await authenticateUser(username, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// CRITICAL: ALWAYS regenerate session ID after authentication
req.session.regenerate((err) => {
if (err) return res.status(500).json({ error: 'Session error' });
req.session.userId = user.id;
req.session.userRole = user.role;
req.session.loginTime = Date.now();
req.session.save((err) => {
if (err) return res.status(500).json({ error: 'Session save error' });
res.json({ success: true });
});
});
});
// Secure logout: destroy session server-side
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) return res.status(500).json({ error: 'Logout error' });
res.clearCookie('__session', {
httpOnly: true,
secure: true,
sameSite: 'strict',
});
res.json({ success: true });
});
});
Anti-Pattern: MemoryStore in Production
Never use express-session's default MemoryStore in production.
It causes memory leaks, does not scale horizontally (sessions lost on restart),
and does not persist sessions across deployments. Always use an external store:
Redis (connect-redis), PostgreSQL (connect-pg-simple),
or MongoDB (connect-mongo). Redis is the preferred choice for
optimal performance and automatic TTL-based key expiration.
Cookie Security: The Flags That Matter
Session cookies must be configured with at least three security attributes. Each one mitigates
a specific category of attacks. The correct combination is HttpOnly + Secure + SameSite,
configured together for layered defense:
The choice between SameSite=Strict and SameSite=Lax depends on the use case.
Strict provides maximum protection but blocks cookies even when users navigate to
your site from an external link (e.g. email), causing a login redirect every time.
Lax allows the cookie for top-level navigations (link clicks) but blocks it for
cross-origin POST/PUT/DELETE requests, offering a good balance between security and usability.
JWT Best Practices and the 5 Fatal Mistakes
JSON Web Tokens are a powerful tool for stateless API authentication, but incorrect implementation
is among the most common causes of critical vulnerabilities. NIST and OWASP identify five fatal
mistakes that completely nullify JWT security, each with devastating consequences if exploited
in production.
The 5 Fatal JWT Mistakes
Mistake #1 - Algorithm "none": Some servers accept tokens with "alg": "none", without a signature. An attacker can forge any payload.
Mistake #2 - Algorithm Confusion: An RS256 server that also accepts HS256 allows the attacker to sign tokens with the known public key.
Mistake #3 - JWT in localStorage: Accessible to any JavaScript running on the page. An XSS on any npm dependency can silently steal the token.
Mistake #4 - Tokens without expiration: An access token that never expires cannot be revoked if compromised.
Mistake #5 - Weak HS256 secret: Short or predictable secrets are vulnerable to offline dictionary or brute force attacks.
Many tutorials recommend storing JWTs in localStorage for simplicity with SPAs.
Never do this for authentication tokens. localStorage is accessible to any
JavaScript running on the page, including third-party libraries. An XSS attack on any npm
dependency can silently steal all tokens. The correct solution is HttpOnly + Secure
+ SameSite=Strict cookies: inaccessible to JavaScript, transmitted only over HTTPS,
with built-in CSRF protection. For SPAs making cross-origin API requests, use
SameSite=None; Secure with an additional CSRF token in the request header.
OAuth 2.1 with PKCE: Secure Federated Authentication
OAuth 2.1 (RFC draft, stable since 2024) consolidates security best practices accumulated over
years of OAuth 2.0 deployment, making PKCE (Proof Key for Code Exchange) mandatory
for all clients, including confidential clients with a client secret. It also eliminates the implicit
flow and password grant, considered inherently insecure and deprecated.
The Authorization Code flow with PKCE works as follows: the client generates a random
code_verifier (43-128 characters), computes the code_challenge
as SHA-256 Base64URL, sends the challenge to the authorization server, and after receiving the
authorization code, exchanges it for a token by presenting the original verifier. Even if an
attacker intercepts the code during the redirect, they cannot exchange it without the verifier.
// OAuth 2.1 PKCE flow - SPA TypeScript client
interface PKCEChallenge {
codeVerifier: string;
codeChallenge: string;
state: string;
}
async function generatePKCEChallenge(): Promise<PKCEChallenge> {
// code_verifier: 64 random characters (accepted range: 43-128)
const codeVerifier = generateRandomString(64);
// code_challenge = BASE64URL(SHA256(ASCII(code_verifier)))
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const digest = await window.crypto.subtle.digest('SHA-256', data);
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
// state: anti-CSRF token unique to this authorization request
const state = generateRandomString(32);
return { codeVerifier, codeChallenge, state };
}
function generateRandomString(length: number): string {
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
return Array.from(array, (byte) =>
byte.toString(36).padStart(2, '0')
).join('').slice(0, length);
}
async function startOAuthFlow(): Promise<void> {
const { codeVerifier, codeChallenge, state } = await generatePKCEChallenge();
// Use sessionStorage (cleared on tab close), not localStorage
sessionStorage.setItem('pkce_verifier', codeVerifier);
sessionStorage.setItem('oauth_state', state);
const params = new URLSearchParams({
response_type: 'code',
client_id: process.env['VITE_OAUTH_CLIENT_ID']!,
redirect_uri: `
#123;window.location.origin}/auth/callback`,
scope: 'openid profile email',
code_challenge: codeChallenge,
code_challenge_method: 'S256', // Always S256, never 'plain'
state: state,
});
window.location.href = `#123;AUTH_SERVER_URL}/authorize?#123;params}`;
}
async function handleOAuthCallback(): Promise<void> {
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const returnedState = params.get('state');
const error = params.get('error');
if (error) {
throw new Error(`OAuth error: #123;error}: #123;params.get('error_description')}`);
}
// Verify anti-CSRF state before proceeding
const savedState = sessionStorage.getItem('oauth_state');
if (!code || returnedState !== savedState) {
throw new Error('Invalid OAuth callback: state mismatch (possible CSRF attack)');
}
const codeVerifier = sessionStorage.getItem('pkce_verifier');
if (!codeVerifier) {
throw new Error('Missing PKCE verifier');
}
// Immediate cleanup
sessionStorage.removeItem('pkce_verifier');
sessionStorage.removeItem('oauth_state');
const response = await fetch(`#123;AUTH_SERVER_URL}/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: process.env['VITE_OAUTH_CLIENT_ID']!,
redirect_uri: `#123;window.location.origin}/auth/callback`,
code: code,
code_verifier: codeVerifier, // Proves we are the legitimate requester
}),
});
if (!response.ok) {
const body = await response.json();
throw new Error(`Token exchange failed: #123;body.error}`);
}
const tokens = await response.json();
await securelyStoreTokens(tokens);
}
WebAuthn and Passkeys: Passwordless Authentication
Passkeys based on WebAuthn (Web Authentication API, W3C standard) represent the most significant
change in authentication in the last twenty years. Unlike passwords, passkeys use asymmetric
public-key cryptography: the private key stays on the user's device protected by biometrics
or device PIN, while the server only knows the public key.
The result is absolute phishing resistance (the key is cryptographically bound
to the origin domain), no passwords to steal in database breaches, and superior UX with Face ID
or Touch ID instead of password plus 2FA. In 2025, over 15 billion accounts
support passkeys. The SimpleWebAuthn library greatly simplifies the implementation.
Multi-factor authentication (MFA) is the most effective countermeasure against credential stuffing
and classic password-based phishing. According to Microsoft, MFA blocks over 99.9% of
automated account attacks. TOTP (Time-based One-Time Password, RFC 6238) is the de facto
standard for software second factors, compatible with Google Authenticator, Authy, 1Password and
any compliant TOTP application.
A critical and often overlooked aspect: the MFA flow must be atomic and non-bypassable.
Never create a partially authenticated session after the first factor that allows any operations.
Instead, use a temporary session with an mfaPending: true flag, and grant full access
only after the second factor is verified.
// MFA TOTP implementation - Node.js
// npm install otpauth qrcode
// NOTE: speakeasy is unmaintained since 2017, use 'otpauth' instead
import * as OTPAuth from 'otpauth';
import QRCode from 'qrcode';
import bcrypt from 'bcrypt';
import crypto from 'crypto';
// Enable 2FA: generate secret and QR code for the user
app.post('/auth/2fa/setup', requireAuth, async (req, res) => {
const user = req.user!;
const totp = new OTPAuth.TOTP({
issuer: 'MyApp',
label: user.email,
algorithm: 'SHA1',
digits: 6,
period: 30,
secret: OTPAuth.Secret.fromRandom(20), // 160 bits of entropy
});
const secretBase32 = totp.secret.base32;
await savePending2FASecret(user.id, secretBase32);
const otpAuthUrl = totp.toString();
const qrCodeDataUrl = await QRCode.toDataURL(otpAuthUrl);
res.json({
secret: secretBase32, // For manual entry
qrCode: qrCodeDataUrl, // QR code base64 data URL
});
});
// Verify and confirm 2FA activation
app.post('/auth/2fa/verify-setup', requireAuth, async (req, res) => {
const { token } = req.body;
const user = req.user!;
const pendingSecret = await getPending2FASecret(user.id);
if (!pendingSecret) {
return res.status(400).json({ error: '2FA setup not initiated' });
}
const totp = new OTPAuth.TOTP({
secret: OTPAuth.Secret.fromBase32(pendingSecret),
algorithm: 'SHA1',
digits: 6,
period: 30,
});
// Validate with window=1 (accepts ±30 seconds for clock skew)
const delta = totp.validate({ token, window: 1 });
if (delta === null) {
return res.status(400).json({ error: 'Invalid TOTP code' });
}
// Generate one-time recovery codes
const recoveryCodes = generateRecoveryCodes(8);
const hashedCodes = await Promise.all(
recoveryCodes.map((code) => bcrypt.hash(code, 12))
);
await enable2FA(user.id, pendingSecret, hashedCodes);
await deletePending2FASecret(user.id);
// Recovery codes in plaintext shown ONCE ONLY
res.json({ success: true, recoveryCodes });
});
// 2FA challenge during login
app.post('/auth/2fa/challenge', async (req, res) => {
const { token } = req.body;
if (!req.session.mfaPending || !req.session.pendingUserId) {
return res.status(401).json({ error: 'Invalid session state' });
}
const user = await getUserById(req.session.pendingUserId);
if (!user?.totpSecret) {
return res.status(400).json({ error: '2FA not configured' });
}
const totp = new OTPAuth.TOTP({
secret: OTPAuth.Secret.fromBase32(user.totpSecret),
algorithm: 'SHA1',
digits: 6,
period: 30,
});
const delta = totp.validate({ token, window: 1 });
if (delta === null) {
await incrementFailedMFAAttempts(user.id);
return res.status(401).json({ error: 'Invalid 2FA code' });
}
await resetFailedMFAAttempts(user.id);
req.session.regenerate((err) => {
if (err) return res.status(500).json({ error: 'Session error' });
req.session.userId = user.id;
req.session.mfaPending = false;
req.session.pendingUserId = undefined;
res.json({ success: true });
});
});
function generateRecoveryCodes(count: number): string[] {
return Array.from({ length: count }, () =>
Array.from({ length: 3 }, () =>
crypto.randomBytes(2).toString('hex').toUpperCase()
).join('-')
);
}
Authorization: RBAC, ABAC and ReBAC
Authentication (who are you?) and authorization (what can you do?) are distinct concepts but often
confused or implemented together in the same middleware. Once identity is verified, the system
must decide if the user has permission for the specific operation requested.
RBAC (Role-Based Access Control) assigns permissions to predefined roles, and roles
to users. It is the simplest model to implement and suitable for most applications. Its limitation
is rigidity: with many tenants or per-resource ownership, the number of roles explodes.
ABAC (Attribute-Based Access Control) evaluates attributes of the user (role,
department), the resource (owner, tenant, classification) and context (time, IP, device).
More flexible than RBAC but more complex to implement and debug. Best for multi-tenant systems.
ReBAC (Relationship-Based Access Control) bases permissions on relationships
between entities in the data graph (e.g. Google Zanzibar). Used by Google Drive, GitHub, Notion.
Ideal for applications with hierarchical ownership structures.
// Hybrid RBAC + ABAC middleware - Node.js Express TypeScript
const ROLE_PERMISSIONS: Record<string, string[]> = {
admin: ['users:read', 'users:write', 'users:delete', 'posts:*'],
editor: ['posts:read', 'posts:write', 'posts:delete'],
viewer: ['posts:read', 'users:read'],
};
// RBAC: check if the user's role has the required permission
function requirePermission(permission: string) {
return (req: Request, res: Response, next: NextFunction) => {
const userRole = req.user?.role;
if (!userRole) {
return res.status(401).json({ error: 'Not authenticated' });
}
const permissions = ROLE_PERMISSIONS[userRole] ?? [];
const hasPermission = permissions.some((p) => {
if (p.endsWith(':*')) {
return permission.startsWith(p.slice(0, -1));
}
return p === permission;
});
if (!hasPermission) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// ABAC: ownership check for resource-level access control
function requireOwnership<T extends { authorId: string }>(
getResource: (id: string) => Promise<T | null>
) {
return async (req: Request, res: Response, next: NextFunction) => {
const resourceId = req.params.id;
const resource = await getResource(resourceId);
if (!resource) {
return res.status(404).json({ error: 'Resource not found' });
}
// Admin bypasses ownership check for support operations
if (req.user?.role === 'admin') {
req.resource = resource;
return next();
}
if (resource.authorId !== req.user?.id) {
return res.status(403).json({ error: 'Access denied' });
}
req.resource = resource;
next();
};
}
// Composition: RBAC + ABAC applied in sequence on the same route
app.put(
'/api/posts/:id',
authenticateJWT, // 1. Authentication: who are you?
requirePermission('posts:write'), // 2. RBAC: can your role write posts?
requireOwnership(getPostById), // 3. ABAC: do you own this post?
async (req: Request, res: Response) => {
const updatedPost = await updatePost(req.params.id, req.body);
res.json(updatedPost);
}
);
Angular: Guards, Interceptors and Client-Side Security
When implementing authentication in an Angular application, the fundamental distinction is:
route guards protect UI navigation, not data security. A malicious user can
always bypass Angular by modifying the URL or calling APIs directly from tools like curl or
Postman. Real security always lives exclusively in the backend.
Route Guards Are Not Security
Angular's CanActivate guards protect UI navigation but do not prevent direct API
access. Every API endpoint must authenticate and authorize completely independently
of any frontend guard. Guards exist only to redirect users to the login page instead
of showing the dashboard, not to protect the underlying data.
OWASP A07 Checklist: Identification and Authentication Failures
OWASP A07:2021 (Identification and Authentication Failures) moved from fourth to seventh position
compared to 2017, thanks to broader MFA and password manager adoption. However, it remains
critical. The following checklist summarizes the minimum controls required to comply with
OWASP 2025 best practices. Remember: 45% of AI-generated code fails security tests
for authentication - always review code generated by GitHub Copilot or ChatGPT against this list.
Secure Authentication Checklist (OWASP 2025)
Password hashing: Argon2id or bcrypt (cost factor >= 12), never MD5/SHA alone
Password policy: Minimum 8 chars, maximum at least 64, check against HaveIBeenPwned
Session fixation: Regenerate session ID after every login with req.session.regenerate()
Cookie flags: HttpOnly + Secure + SameSite on all session and auth cookies
Logout: Destroy the server-side session, not just clear the cookie
Session timeout: Inactivity timeout (30-60 min for sensitive data)
JWT algorithm: Explicit whitelist during verification, block "none" and unexpected algorithms
JWT storage: HttpOnly cookie, never localStorage or sessionStorage
Brute force: IP rate limiting + account lockout with user notification
User enumeration: Generic response for invalid credentials, timing-safe comparison
MFA: TOTP available for all users, mandatory for admin/privileged roles
OAuth 2.1: PKCE mandatory, no implicit flow, no password grant
Passkeys: Consider as passwordless option for new projects in 2025+
Audit log: Log all auth events with IP, user agent and timestamp
Angular guards: UI only, real security always in the backend
CSRF: CSRF token for SPAs using session cookies cross-origin
Conclusion
Secure authentication in 2025 requires a multi-layer strategy: strong password hashing with
Argon2id, correct session management with HttpOnly+Secure+SameSite cookies, rate limiting
and account lockout against brute force, MFA with TOTP for all privileged users, and OAuth 2.1
with PKCE for federated authentication. Each layer mitigates a specific attack category;
skipping even one opens real vulnerability windows.
The most significant change for the coming years is the transition to passkeys:
phishing-resistant by design, no passwords to remember or hash in the database, with superior UX
through Face ID and Touch ID. If you are building a new application in 2025, seriously consider
passkeys as your primary authentication factor.
A final critical note about AI-generated code: 45% of authentication code generated by AI tools
fails security tests. The most common issues are sessions without ID regeneration after login,
JWTs stored in localStorage, missing timing-safe comparisons, and cookies without the correct
security flags. Always use this OWASP checklist as a final code review, regardless of who
(or what) wrote it.