# ✅ Parte 1: Guida completa per installare e configurare Keycloak come server OAuth2/OIDC locale ## Step 1: Scarica e avvia Keycloak Vai su (https://www.keycloak.org/downloads) Scarica la versione Quarkus distribution. Avvia in modalità sviluppo: ./bin/kc.sh start-dev Keycloak sarà disponibile su: http://localhost:8080 ## Step 2: Configura Realm e Client Accedi alla console admin: http://localhost:8080/admin. Crea un Realm (es. flutter-realm). Crea un Client: Tipo: Public Nome: flutter-app Redirect URI: com.tuaapp:/oauthredirect Abilita Standard Flow (Authorization Code). Salva il Client ID. ## Step 3: Configura utenti Vai su Users → Aggiungi utente → Imposta credenziali. Ora hai un utente locale per il login. ## Step 4: Sicurezza In locale puoi usare HTTP, ma in produzione serve HTTPS. Abilita PKCE per sicurezza (Keycloak lo supporta di default). # ✅ Parte 2: Esempio Flutter che si collega a Keycloak locale Dipendenze ```sh dependencies: flutter_appauth: ^6.0.0 flutter_secure_storage: ^9.0.0 ``` Codice ```sh import 'package:flutter_appauth/flutter_appauth.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; final FlutterAppAuth appAuth = FlutterAppAuth(); final storage = FlutterSecureStorage(); Future login() async { final AuthorizationTokenResponse? result = await appAuth.authorizeAndExchangeCode( AuthorizationTokenRequest( 'flutter-app', // Client ID 'com.tuaapp:/oauthredirect', // Redirect URI issuer: 'http://localhost:8080/realms/flutter-realm', scopes: ['openid', 'profile', 'email'], ), ); if (result != null) { await storage.write(key: 'access_token', value: result.accessToken); await storage.write(key: 'id_token', value: result.idToken); print('Login OK: ${result.accessToken}'); } } ``` Refresh Token ```sh Future refreshToken() async { final refreshToken = await storage.read(key: 'refresh_token'); if (refreshToken != null) { final TokenResponse? response = await appAuth.token( TokenRequest( 'flutter-app', 'com.tuaapp:/oauthredirect', refreshToken: refreshToken, issuer: 'http://localhost:8080/realms/flutter-realm', ), ); if (response != null) { await storage.write(key: 'access_token', value: response.accessToken); } } } ``` # ✅ Dockerfile per Keycloak Questo crea un container con Keycloak in modalità sviluppo: Dockerfile ```sh FROM quay.io/keycloak/keycloak:24.0.4 # Imposta la modalità sviluppo ENV KC_DB=dev-mem ENV KC_HTTP_PORT=8080 ENV KC_HOSTNAME=localhost # Avvia Keycloak in modalità sviluppo ``` Build e Run ```sh docker build -t keycloak-local . docker run -p 8080:8080 keycloak-local ``` ## ✅ docker-compose.yml Se vuoi aggiungere persistenza e database (PostgreSQL): ```sh version: '3.8' services: keycloak: image: quay.io/keycloak/keycloak:24.0.4 container_name: keycloak environment: KC_DB: postgres KC_DB_URL_HOST: postgres KC_DB_URL_DATABASE: keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: keycloak KC_HOSTNAME: localhost ports: - "8080:8080" command: - start-dev depends_on: - postgres postgres: image: postgres:15 container_name: postgres environment: POSTGRES_DB: keycloak POSTGRES_USER: keycloak POSTGRES_PASSWORD: keycloak ports: - "5432:5432" ``` ## ✅ Come usarlo Salva il file come docker-compose.yml. e avvia con docker-compose up -d Accedi a Keycloak su http://localhost:8080. Crea Realm, Client, e imposta il Redirect URI per la tua app Flutter. ## ✅ Script Bash: init-keycloak.sh ```sh #!/bin/bash # Variabili KEYCLOAK_URL="http://localhost:8080" REALM_NAME="mio-realm" CLIENT_ID="flutter-app" REDIRECT_URI="com.tuaapp:/oauthredirect" ADMIN_USER="admin" ADMIN_PASS="admin" # Login come admin /opt/keycloak/bin/kcadm.sh config credentials \ --server $KEYCLOAK_URL \ --realm master \ --user $ADMIN_USER \ --password $ADMIN_PASS # Crea Realm /opt/keycloak/bin/kcadm.sh create realms \ -s realm=$REALM_NAME \ -s enabled=true # Crea Client /opt/keycloak/bin/kcadm.sh create clients -r $REALM_NAME \ -s clientId=$CLIENT_ID \ -s enabled=true \ -s publicClient=true \ -s redirectUris="[\"$REDIRECT_URI\"]" \ -s protocol=openid-connect \ -s standardFlowEnabled=true \ -s directAccessGrantsEnabled=true echo "✅ Realm '$REALM_NAME' e Client '$CLIENT_ID' creati con successo!" ``` ### ✅ Come usarlo con Docker Copia lo script in una cartella. Aggiorna docker-compose.yml aggiungendo un volume per montare lo script: ```sh volumes: - ./init-keycloak.sh:/opt/keycloak/init-keycloak.sh ``` Dopo che il container è avviato, esegui: docker exec -it keycloak bash /opt/keycloak/init-keycloak.sh ## Nginx config ```sh server { listen 443 ssl; server_name keycloak.local; ssl_certificate /etc/nginx/ssl/keycloak.crt; ssl_certificate_key /etc/nginx/ssl/keycloak.key; location / { proxy_pass http://keycloak:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; } } server { listen 80; server_name keycloak.local; return 301 https://$host$request_uri; } ``` ## Flutter 1. Dipendenze Nel pubspec.yaml: ```sh dependencies: flutter_appauth: ^6.0.0 flutter_secure_storage: ^9.0.0 http: ^1.2.0 ``` 2. Configurazione Keycloak Realm: mio-realm Client: flutter-app Redirect URI: com.tuaapp:/oauthredirect Abilita: standardFlowEnabled=true directAccessGrantsEnabled=true (per registrazione via API) 3. Codice Flutter Ecco il file auth_service.dart: ```sh import 'package:flutter_appauth/flutter_appauth.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; class AuthService { final FlutterAppAuth _appAuth = FlutterAppAuth(); final FlutterSecureStorage _secureStorage = const FlutterSecureStorage(); final String clientId = 'flutter-app'; final String redirectUri = 'com.tuaapp:/oauthredirect'; final String issuer = 'https://auth.patachina.it/realms/mio-realm'; final List scopes = ['openid', 'profile', 'email']; Future login() async { final result = await _appAuth.authorizeAndExchangeCode( AuthorizationTokenRequest( clientId, redirectUri, issuer: issuer, scopes: scopes, ), ); if (result != null) { await _secureStorage.write(key: 'access_token', value: result.accessToken); await _secureStorage.write(key: 'refresh_token', value: result.refreshToken); await _secureStorage.write(key: 'id_token', value: result.idToken); } } Future logout() async { await _secureStorage.deleteAll(); } Future register(String username, String password, String email) async { final url = '$issuer/protocol/openid-connect/token'; // Usa le API Admin di Keycloak o un endpoint custom // Qui esempio con Direct Access Grant (non consigliato in produzione) final response = await http.post( Uri.parse('$issuer/protocol/openid-connect/token'), headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: { 'client_id': clientId, 'grant_type': 'password', 'username': username, 'password': password, }, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); await _secureStorage.write(key: 'access_token', value: data['access_token']); await _secureStorage.write(key: 'refresh_token', value: data['refresh_token']); } else { throw Exception('Errore registrazione/login'); } } } ``` 4. UI di esempio Nel main.dart: ```sh import 'package:flutter/material.dart'; import 'auth_service.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { final AuthService authService = AuthService(); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Login & Registrazione')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton(onPressed: authService.login, child: Text('Login')), ElevatedButton(onPressed: () => authService.register('user', 'pass', 'email@test.com'), child: Text('Registrati')), ElevatedButton(onPressed: authService.logout, child: Text('Logout')), ], ), ), ), ); } } ```