Passa al contenuto principale

Sistema Autenticazione

Il sistema di autenticazione di Emblema è basato su Keycloak come Identity Provider, con integrazione OpenID Connect per SSO e un sistema RBAC (Role-Based Access Control) avanzato a 3 livelli di permessi.

Loading diagram...

Keycloak

Keycloak serve come Identity and Access Management (IAM) solution, fornendo autenticazione centralizzata e gestione degli utenti con supporto per temi personalizzati.

Configurazione Realm

Il realm "emblema" è configurato con client specifici per l'applicazione web:

{
"realm": "emblema",
"enabled": true,
"clients": [
{
"clientId": "web_client",
"name": "Emblema Web",
"enabled": true,
"clientAuthenticatorType": "client-secret",
"secret": "o4W2t7tIqnkxtQMI8b5foCs8GgvVs7Xl",
"redirectUris": [
"https://www.emblema.aeronautica.difesa.it/api/auth/callback/keycloak"
],
"standardFlowEnabled": true,
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"protocol": "openid-connect",
"attributes": {
"login_theme": "Emblema",
"post.logout.redirect.uris": "https://www.emblema.aeronautica.difesa.it"
}
}
]
}

Deployment Docker

# docker-compose-auth.yaml
keycloak:
image: quay.io/keycloak/keycloak:26.2
volumes:
- ./config/keycloak/providers:/opt/keycloak/providers
- ./config/keycloak/realm-config.json:/opt/keycloak/data/import/emblema.json
command: ["start", "--import-realm"]
environment:
KC_HOSTNAME: ${KEYCLOAK_HOSTNAME}
KEYCLOAK_FRONTEND_URL: https://${KEYCLOAK_HOSTNAME}
KC_HTTP_ENABLED: true
KC_HOSTNAME_STRICT_HTTPS: false
KC_PROXY_HEADERS: "xforwarded"
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres-keycloak/keycloak_db

Tema Personalizzato

Keycloak utilizza un tema personalizzato "Emblema" per l'interfaccia di login:

  • Provider JAR: /config/keycloak/providers/keycloak-theme-for-kc-*.jar
  • Login Theme: Personalizzato con branding Emblema
  • Logout Redirect: Configurato per tornare all'applicazione principale

Groups e Organizzazioni

Keycloak gestisce groups per l'organizzazione utenti:

// Configurazione gruppi autorizzati
const ALLOWED_GROUPS = process.env.ALLOWED_GROUPS.split(",")
.map((val) => val.trim())
.filter(Boolean);

// Esempio: "admin,user,viewer"

JWT Tokens

I JWT token seguono lo standard OpenID Connect con payload personalizzato per groups e permissions.

Struttura Token

{
"exp": 1640995200,
"iat": 1640908800,
"auth_time": 1640908800,
"jti": "uuid",
"iss": "https://keycloak.emblema.ai/realms/emblema",
"aud": "web_client",
"sub": "user-uuid",
"typ": "Bearer",
"azp": "web_client",
"realm_access": {
"roles": ["default-roles-emblema"]
},
"resource_access": {
"web_client": {
"roles": ["user"]
}
},
"scope": "openid email profile",
"email_verified": true,
"name": "John Doe",
"preferred_username": "john.doe",
"given_name": "John",
"family_name": "Doe",
"email": "john.doe@example.com",
"groups": ["admin", "user"]
}

Verifica JWT

La verifica dei token utilizza JOSE per validare contro il JWKS endpoint:

// apps/www-emblema/lib/keycloak.ts
import { jwtVerify, createRemoteJWKSet } from "jose";

export async function verifyJWT(token: string) {
const JWKS = createRemoteJWKSet(
new URL(
`${process.env.AUTH_KEYCLOAK_ISSUER}/protocol/openid-connect/certs`,
),
);

const { payload } = await jwtVerify(token, JWKS);
return payload;
}

Token Lifecycle

Loading diagram...

SSO (Single Sign-On)

Emblema implementa SSO attraverso OpenID Connect con flusso standard authorization code.

Integrazione NextAuth.js

// apps/www-emblema/lib/auth.ts
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Keycloak({
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) {
// Decode per estrarre groups
const decoded = JSON.parse(
Buffer.from(account.access_token.split(".")[1], "base64").toString(),
);
token.groups = decoded.groups ?? [];
}
return token;
},
async session({ session, token }) {
session.accessToken = token.accessToken as string;
session.groups = token.groups as string[];
return session;
},
},
});

Controllo Accesso Groups

// Validazione gruppi autorizzati
async signIn({ user, account, profile }) {
if (account?.access_token) {
const decoded = JSON.parse(
Buffer.from(account.access_token.split(".")[1], "base64").toString()
);
const userGroups = decoded.groups ?? [];

const hasAuthorizedGroup = userGroups.some(group =>
ALLOWED_GROUPS.includes(group)
);

if (!hasAuthorizedGroup && ALLOWED_GROUPS.length > 0) {
return false; // Accesso negato
}
}
return true;
}

Route Protection

// Middleware di protezione routes
export default withAuth(
function middleware(req) {
// Logica middleware
},
{
callbacks: {
authorized: ({ token, req }) => {
// Verifica autorizzazioni per route specifica
return hasAuthorizedGroups(token?.groups);
},
},
},
);

RBAC (Role-Based Access Control)

Emblema implementa un sistema RBAC a 3 livelli gerarchici per un controllo granulare degli accessi.

Architettura Permessi

Loading diagram...

Schema Database Permessi

-- Entity Permissions - Permessi su moduli interi
CREATE TABLE "public"."entity_permission" (
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
"entity_type" text NOT NULL, -- 'Document', 'KnowledgeBase', etc.
"user_id" uuid NOT NULL,
"permission" text NOT NULL, -- 'CREATE', 'READ', 'UPDATE', 'DELETE'
"created_by" uuid NOT NULL,
"created_at" timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY ("id")
);

-- Record Permissions - Permessi su singoli record
CREATE TABLE "public"."record_permission" (
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
"entity_id" uuid NOT NULL, -- ID del record specifico
"entity_type" text NOT NULL, -- Tipo entità
"user_id" uuid NOT NULL,
"permission" text NOT NULL, -- 'READ', 'UPDATE', 'DELETE'
"parent_id" uuid, -- Gerarchia permessi
"created_by" uuid NOT NULL,
"created_at" timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY ("id")
);

Hook Permessi Frontend

// apps/www-emblema/hooks/use-permission.ts
export const usePermission = () => {
const params = useParams();
const session = useSession();

// Fetch permessi entity-level
const { data: entityPermissions } = useSWR(
`/api/v1/EntityPermission`,
fetcher,
);

// Fetch permessi record-level
const { data: recordPermissions } = useSWR(
`/api/v1/${params.entityType}/${params.entityId}/RecordPermission`,
fetcher,
);

return {
canCreate: recordPermission?.create || entityPermission?.create,
canRead: recordPermission?.read || entityPermission?.read,
canUpdate: recordPermission?.update || entityPermission?.update,
canDelete: recordPermission?.delete || entityPermission?.delete,
};
};

Logica Autorizzazione

// Esempio di autorizzazione composita
function hasPermission(
user: User,
action: string,
entityType: string,
entityId?: string,
): boolean {
// 1. Verifica Group membership
if (!hasAuthorizedGroups(user.groups)) {
return false;
}

// 2. Verifica Entity Permission
const entityPermission = getEntityPermission(user.id, entityType, action);

// 3. Verifica Record Permission (se specificato)
if (entityId) {
const recordPermission = getRecordPermission(
user.id,
entityId,
entityType,
action,
);
return recordPermission || entityPermission;
}

return entityPermission;
}

Permessi Gerarchici

I permessi seguono una logica gerarchica:

  1. Groups: Controllo accesso applicazione
  2. Entity Permissions: Operazioni su tipi di entità
  3. Record Permissions: Accesso specifico per singoli record
// Esempio di gerarchia permessi
const permissionHierarchy = {
admin: {
entityPermissions: ["*"], // Tutti i moduli
recordPermissions: ["*"], // Tutti i record
},
user: {
entityPermissions: [
"Document.CREATE",
"Document.READ",
"KnowledgeBase.READ",
],
recordPermissions: ["own_documents.*"], // Solo documenti propri
},
viewer: {
entityPermissions: ["Document.READ", "KnowledgeBase.READ"],
recordPermissions: ["shared_documents.READ"], // Solo lettura documenti condivisi
},
};

Guard Components

// Componente di protezione UI
export function PermissionGuard({
children,
fallback,
entityType,
entityId,
action
}: PermissionGuardProps) {
const { canPerformAction } = usePermission();

if (!canPerformAction(action, entityType, entityId)) {
return fallback || <UnauthorizedMessage />;
}

return <>{children}</>;
}

// Utilizzo
<PermissionGuard
entityType="Document"
action="DELETE"
entityId={documentId}
fallback={<span>Non autorizzato</span>}
>
<DeleteButton />
</PermissionGuard>

Integrazione Backend Services

Middleware Autenticazione API

// Middleware per API REST
export async function authMiddleware(req: Request) {
const token = req.headers.get("authorization")?.split(" ")[1];

if (!token) {
throw new Error("Token mancante");
}

const decoded = await verifyJWT(token);
const userId = await getUserFromToken(decoded);

return { userId, groups: decoded.groups };
}

Hasura JWT Configuration

# Configurazione Hasura per JWT
HASURA_GRAPHQL_JWT_SECRET: |
{
"type": "RS256",
"jwk_url": "https://keycloak.emblema.ai/realms/emblema/protocol/openid-connect/certs",
"claims_map": {
"x-hasura-allowed-roles": {"path": "$.groups"},
"x-hasura-default-role": "user",
"x-hasura-user-id": {"path": "$.sub"}
}
}

Service-to-Service Authentication

I servizi backend utilizzano service accounts per comunicazioni interne:

# Service account authentication per background-task
class ServiceAuth:
def __init__(self):
self.client_id = os.getenv("SERVICE_CLIENT_ID")
self.client_secret = os.getenv("SERVICE_CLIENT_SECRET")

async def get_service_token(self):
# OAuth2 Client Credentials flow
response = await client.post(
f"{KEYCLOAK_URL}/realms/emblema/protocol/openid-connect/token",
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
)
return response.json()["access_token"]

Sicurezza e Best Practices

Token Security

  • HTTPS Only: Tutti i token sono trasmessi solo su connessioni sicure
  • Short Expiry: Access token con durata limitata (15 minuti)
  • Refresh Rotation: Refresh token con rotazione automatica
  • Secure Storage: Token memorizzati in httpOnly cookies

Session Management

// Configurazione sicura sessioni
export const authConfig = {
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 giorni
updateAge: 24 * 60 * 60, // Aggiorna ogni 24 ore
},
cookies: {
sessionToken: {
name: `__Secure-next-auth.session-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: true,
},
},
},
};

Rate Limiting e Monitoring

// Rate limiting per endpoint di autenticazione
export const authRateLimit = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minuti
max: 5, // Max 5 tentativi di login
message: "Troppi tentativi di login",
standardHeaders: true,
legacyHeaders: false,
});

Riferimenti

Prossimi Passi

  1. Esplora Frontend: Architettura Next.js 14 e componenti UI
  2. Comprendi API: Struttura REST e GraphQL endpoints
  3. Analizza Task Processing: Sistema asincrono e background jobs
  4. Approfondisci Monitoring: Logging e metriche di sicurezza

Questa pagina ti è stata utile?