No description
Find a file
2025-11-12 19:14:32 +08:00
README.md Aggiorna README.md 2025-11-12 19:14:32 +08:00

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

dependencies:
  flutter_appauth: ^6.0.0
  flutter_secure_storage: ^9.0.0

Codice

import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

final FlutterAppAuth appAuth = FlutterAppAuth();
final storage = FlutterSecureStorage();

Future<void> 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

Future<void> 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

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

docker build -t keycloak-local .
docker run -p 8080:8080 keycloak-local

docker-compose.yml

Se vuoi aggiungere persistenza e database (PostgreSQL):

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

#!/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:

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

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:

dependencies:
  flutter_appauth: ^6.0.0
  flutter_secure_storage: ^9.0.0
  http: ^1.2.0
  1. Configurazione Keycloak

Realm: mio-realm

Client: flutter-app

Redirect URI: com.tuaapp:/oauthredirect

Abilita:

standardFlowEnabled=true

directAccessGrantsEnabled=true (per registrazione via API)

  1. Codice Flutter

Ecco il file auth_service.dart:

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<String> scopes = ['openid', 'profile', 'email'];

  Future<void> 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<void> logout() async {
    await _secureStorage.deleteAll();
  }

  Future<void> 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');
    }
  }
}
  1. UI di esempio

Nel main.dart:

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')),
            ],
          ),
        ),
      ),
    );
  }
}