Passa al contenuto principale

Autenticazione e Autorizzazione

Emblema utilizza un sistema di autenticazione basato su JWT (JSON Web Tokens) integrato con Keycloak per la gestione centralizzata di identità e accessi.

Panoramica

Metodi di Autenticazione

  1. NextAuth.js - Per applicazioni web con sessioni
  2. JWT Bearer Tokens - Per API e integrazione servizi
  3. API Keys - Per applicazioni di sistema (opzionale)

Flusso di Autenticazione

Loading diagram...

Configurazione Keycloak

Endpoint di Autenticazione

# Base URL Keycloak
KEYCLOAK_BASE_URL=https://keycloak.your-domain.com

# Realm Emblema
KEYCLOAK_REALM=emblema

# Token endpoint
TOKEN_ENDPOINT=/auth/realms/emblema/protocol/openid-connect/token

# UserInfo endpoint
USERINFO_ENDPOINT=/auth/realms/emblema/protocol/openid-connect/userinfo

Client Configuration

// apps/www-emblema/lib/auth.ts
export const authConfig = {
providers: [
KeycloakProvider({
clientId: process.env.AUTH_KEYCLOAK_ID,
clientSecret: process.env.AUTH_KEYCLOAK_SECRET,
issuer: process.env.AUTH_KEYCLOAK_ISSUER,
authorization: {
params: { scope: "openid email profile" },
},
}),
],
callbacks: {
async jwt({ token, account }) {
if (account?.access_token) {
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token;

// Decodifica gruppi utente
const decoded = JSON.parse(
Buffer.from(account.access_token.split(".")[1], "base64").toString(),
);
token.groups = decoded.groups ?? [];
}
return token;
},
},
};

Ottenere un JWT Token

1. Password Grant (Sviluppo)

curl -X POST https://keycloak.your-domain.com/auth/realms/emblema/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "client_id=emblema-client" \
-d "client_secret=your-client-secret" \
-d "username=user@example.com" \
-d "password=user-password"

Risposta:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJr...",
"expires_in": 3600,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJh...",
"token_type": "Bearer",
"not-before-policy": 0,
"session_state": "c4f5d9f1-8b2a-4c3d-9e1f-2a3b4c5d6e7f",
"scope": "openid email profile"
}

2. Authorization Code (Produzione)

Per applicazioni web, usa il flusso OAuth2 Authorization Code:

// Frontend redirect per login
window.location.href =
`https://keycloak.your-domain.com/auth/realms/emblema/protocol/openid-connect/auth?` +
`client_id=emblema-client&` +
`redirect_uri=${encodeURIComponent(window.location.origin + "/api/auth/callback/keycloak")}&` +
`response_type=code&` +
`scope=openid%20email%20profile`;

Utilizzo del Token

Headers Richiesti

Tutte le chiamate API devono includere:

Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json
Accept-Language: it # Opzionale per i18n

Esempio Completo

# Ottenimento token
TOKEN=$(curl -s -X POST https://keycloak.your-domain.com/auth/realms/emblema/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&client_id=emblema-client&username=user@example.com&password=password" \
| jq -r '.access_token')

# Utilizzo del token per chiamata API
curl -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept-Language: it" \
https://your-domain.com/api/v1/document

Autorizzazione Granulare

Sistema di Gruppi

Emblema utilizza gruppi Keycloak per controllare l'accesso:

// apps/www-emblema/lib/auth.ts
export const ALLOWED_GROUPS = [
'emblema-users',
'emblema-admins',
'emblema-developers'
];

// Verifica autorizzazione gruppo
export const hasAuthorizedGroups = (groups?: string[]): boolean => {
if (!ALLOWED_GROUPS.length) return true;
if (!groups?.length) return false;

return groups.some(group => ALLOWED_GROUPS.includes(group));
};

Permessi a Livello di Entità

Ogni entità ha permessi granulari:

// Controllo permessi per entità specifica
const hasPermission = await hasEntityTypePermission({
userId: await getUserId(req),
entityType: EntityType.Document,
read: true, // Lettura
create: true, // Creazione
update: true, // Modifica
delete: true, // Eliminazione
});

Record Permissions

Permessi a livello di singolo record:

{
"recordPermissions": {
"data": {
"userId": "user-uuid",
"read": true,
"create": true,
"update": true,
"delete": true,
"entityType": "Document"
}
}
}

Token Validation & Claims

Struttura JWT Token

{
"header": {
"alg": "RS256",
"typ": "JWT",
"kid": "keycloak-key-id"
},
"payload": {
"exp": 1640995200,
"iat": 1640991600,
"iss": "https://keycloak.your-domain.com/auth/realms/emblema",
"aud": "emblema-client",
"sub": "user-uuid",
"typ": "Bearer",
"azp": "emblema-client",
"session_state": "session-uuid",
"scope": "openid email profile",
"email_verified": true,
"preferred_username": "user@example.com",
"email": "user@example.com",
"groups": ["emblema-users"]
}
}

Validazione Server-Side

// apps/www-emblema/lib/keycloak.ts
import jwt from "jsonwebtoken";

export const verifyJWT = async (token: string) => {
try {
const decoded = jwt.verify(token, publicKey, {
issuer: process.env.AUTH_KEYCLOAK_ISSUER,
audience: process.env.AUTH_KEYCLOAK_ID,
});
return decoded;
} catch (error) {
throw new Error("Invalid token");
}
};

Gestione Errori di Autenticazione

Codici di Errore

CodiceDescrizioneAzione
401Token mancante o non validoRi-effettua il login
403Permessi insufficientiContatta amministratore
403Gruppo non autorizzatoRichiedi accesso

Esempi di Risposta

401 Unauthorized

{
"error": "Unauthorized",
"code": "UNAUTHORIZED",
"message": "No valid token found",
"timestamp": "2024-01-15T10:00:00Z"
}

403 Forbidden - Gruppo

{
"error": "Forbidden",
"code": "NOT_AUTHORIZED",
"message": "User group not in allowed groups",
"details": {
"userGroups": ["public-users"],
"allowedGroups": ["emblema-users", "emblema-admins"]
}
}

403 Forbidden - Permessi Entità

{
"error": "Forbidden",
"code": "NOT_AUTHORIZED",
"message": "You are not authorized to create this entity",
"details": {
"entityType": "Document",
"requiredPermission": "create"
}
}

Refresh Token

Rinnovo Automatico

const refreshToken = async (refresh_token) => {
const response = await fetch(
"https://keycloak.your-domain.com/auth/realms/emblema/protocol/openid-connect/token",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "refresh_token",
client_id: "emblema-client",
client_secret: "your-client-secret",
refresh_token: refresh_token,
}),
},
);

return response.json();
};

Interceptor per Auto-Refresh

// Axios interceptor per gestione automatica refresh
axios.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
try {
const newToken = await refreshToken(storedRefreshToken);
error.config.headers.Authorization = `Bearer ${newToken.access_token}`;
return axios.request(error.config);
} catch (refreshError) {
// Redirect to login
window.location.href = "/auth/signin";
}
}
return Promise.reject(error);
},
);

Best Practices

Sicurezza

  1. Never log tokens - Non loggare mai token JWT nei log
  2. HTTPS only - Usa sempre HTTPS in produzione
  3. Short expiration - Configura token con breve durata (1-2 ore)
  4. Secure storage - Memorizza refresh token in modo sicuro
  5. Validate claims - Sempre validare iss, aud, exp

Performance

  1. Cache public keys - Cachea le chiavi pubbliche Keycloak
  2. Token pooling - Riusa token validi per richieste multiple
  3. Lazy validation - Valida solo quando necessario

Development

# .env.local per sviluppo
AUTH_KEYCLOAK_ID=emblema-dev
AUTH_KEYCLOAK_SECRET=dev-secret
AUTH_KEYCLOAK_ISSUER=http://localhost:8080/auth/realms/emblema
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=dev-secret-key

Production

# .env.production
AUTH_KEYCLOAK_ID=emblema-prod
AUTH_KEYCLOAK_SECRET=secure-production-secret
AUTH_KEYCLOAK_ISSUER=https://keycloak.your-domain.com/auth/realms/emblema
NEXTAUTH_URL=https://your-domain.com
NEXTAUTH_SECRET=secure-production-nextauth-secret

Questa pagina ti è stata utile?