Implementieren Sie OAuth2 in Flutter Web mit dieser Schritt-für-Schritt-Anleitung. Erfahren Sie, wie Sie eine sichere Authentifizierung für Ihre Cross-Platform-Apps einrichten.
OAuth2 mit Flutter Web: Sichere Authentifizierung für Cross-Platform-Apps
Als führende App-Agentur aus Köln mit Spezialisierung auf Flutter-Entwicklung teilen wir heute unsere bewährte Implementierung von OAuth2 in Flutter Web. Diese Lösung haben wir in zahlreichen Kundenprojekten erfolgreich eingesetzt und kontinuierlich verbessert.
OAuth2 ist der Industriestandard für sichere Authentifizierung, und mit Flutter können wir diese Funktionalität plattformübergreifend implementieren - sowohl für mobile Apps als auch für Web-Anwendungen. In diesem Artikel zeigen wir Ihnen, wie Sie OAuth2 in Ihrer Flutter Web-Anwendung einrichten.
Schritt 1: Abhängigkeiten hinzufügen
Fügen Sie die erforderlichen Pakete zu Ihrer pubspec.yaml
Datei hinzu:
dependencies:
flutter:
sdk: flutter
http: ^0.13.3
shared_preferences: ^2.0.6
rxdart: ^0.27.1
get_it: ^7.1.3
url_launcher: ^6.0.9
jwt_decoder: ^2.0.1
Schritt 2: AuthService implementieren
Erstellen Sie eine auth_service.dart
Datei mit folgendem Inhalt:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:rxdart/rxdart.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:get_it/get_it.dart';
final getIt = GetIt.instance;
class AuthService {
final SharedPreferences _prefs = getIt.get<SharedPreferences>();
final BehaviorSubject<bool> _isLoggedInController = BehaviorSubject<bool>.seeded(false);
Stream<bool> get isLoggedIn => _isLoggedInController.stream;
// OAuth2 Konfiguration
final String clientId = 'YOUR_CLIENT_ID';
final String clientSecret = 'YOUR_CLIENT_SECRET';
final String redirectUri = kIsWeb
? 'http://localhost:8080/callback.html'
: 'com.yourcompany.app://callback';
final String authorizationEndpoint = 'https://your-auth-provider.com/authorize';
final String tokenEndpoint = 'https://your-auth-provider.com/oauth/token';
final String scope = 'openid profile email';
AuthService() {
_checkIfLoggedIn();
}
Future<void> _checkIfLoggedIn() async {
final accessToken = _prefs.getString('access_token');
final refreshToken = _prefs.getString('refresh_token');
final expiresAt = _prefs.getInt('expires_at');
if (accessToken != null && expiresAt != null) {
if (DateTime.now().millisecondsSinceEpoch < expiresAt) {
_isLoggedInController.add(true);
} else if (refreshToken != null) {
try {
await _refreshToken(refreshToken);
_isLoggedInController.add(true);
} catch (e) {
_isLoggedInController.add(false);
}
} else {
_isLoggedInController.add(false);
}
} else {
_isLoggedInController.add(false);
}
}
Future<void> loginAction() async {
final authUrl = Uri.parse(authorizationEndpoint).replace(
queryParameters: {
'client_id': clientId,
'redirect_uri': redirectUri,
'response_type': 'code',
'scope': scope,
},
);
if (await canLaunch(authUrl.toString())) {
await launch(authUrl.toString());
} else {
throw 'Could not launch $authUrl';
}
}
Future<void> doAuthOnWeb(Map<String, String> queryParams) async {
if (queryParams.containsKey('code')) {
final code = queryParams['code'];
await _getToken(code!);
}
}
Future<void> _getToken(String code) async {
final response = await http.post(
Uri.parse(tokenEndpoint),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: {
'grant_type': 'authorization_code',
'client_id': clientId,
'client_secret': clientSecret,
'code': code,
'redirect_uri': redirectUri,
},
);
if (response.statusCode == 200) {
final Map<String, dynamic> data = json.decode(response.body);
await _prefs.setString('access_token', data['access_token']);
await _prefs.setString('id_token', data['id_token']);
await _prefs.setString('refresh_token', data['refresh_token']);
final expiresIn = data['expires_in'] as int;
final expiresAt = DateTime.now().millisecondsSinceEpoch + expiresIn * 1000;
await _prefs.setInt('expires_at', expiresAt);
if (data.containsKey('id_token')) {
final Map<String, dynamic> profile = parseIdToken(data['id_token']);
await _prefs.setString('user_profile', json.encode(profile));
}
_isLoggedInController.add(true);
} else {
throw Exception('Failed to get token');
}
}
Future<void> _refreshToken(String refreshToken) async {
final response = await http.post(
Uri.parse(tokenEndpoint),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: {
'grant_type': 'refresh_token',
'client_id': clientId,
'client_secret': clientSecret,
'refresh_token': refreshToken,
},
);
if (response.statusCode == 200) {
final Map<String, dynamic> data = json.decode(response.body);
await _prefs.setString('access_token', data['access_token']);
if (data.containsKey('refresh_token')) {
await _prefs.setString('refresh_token', data['refresh_token']);
}
final expiresIn = data['expires_in'] as int;
final expiresAt = DateTime.now().millisecondsSinceEpoch + expiresIn * 1000;
await _prefs.setInt('expires_at', expiresAt);
_isLoggedInController.add(true);
} else {
throw Exception('Failed to refresh token');
}
}
Map<String, dynamic> parseIdToken(String idToken) {
return JwtDecoder.decode(idToken);
}
Future<String?> getAccessTokenIfLoggedIn() async {
final accessToken = _prefs.getString('access_token');
final expiresAt = _prefs.getInt('expires_at');
if (accessToken != null && expiresAt != null) {
if (DateTime.now().millisecondsSinceEpoch < expiresAt) {
return accessToken;
} else {
final refreshToken = _prefs.getString('refresh_token');
if (refreshToken != null) {
try {
await _refreshToken(refreshToken);
return _prefs.getString('access_token');
} catch (e) {
return null;
}
}
}
}
return null;
}
Future<void> logout() async {
await _prefs.remove('access_token');
await _prefs.remove('id_token');
await _prefs.remove('refresh_token');
await _prefs.remove('expires_at');
await _prefs.remove('user_profile');
_isLoggedInController.add(false);
}
void dispose() {
_isLoggedInController.close();
}
}
Schritt 3: Callback-Seite erstellen
Erstellen Sie eine callback.html
im web
-Ordner Ihres Flutter-Projekts:
<html>
<body>
</body>
<script>
function findGetParameter(parameterName) {
var result = null,
tmp = [];
location.search
.substr(1)
.split("&")
.forEach(function (item) {
tmp = item.split("=");
if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);
});
return result;
}
let code = findGetParameter('code');
// Get Hostname
var url = window.location.href
var arr = url.split("/");
var currentUrl = arr[0] + "//" + arr[2]
// Build new URL
let newUrl = currentUrl + "/#/callback?code=" + code;
// Send to new URL
window.location.href = newUrl;
</script>
</html>
Schritt 4: Dependency Injection einrichten
Initialisieren Sie die Dependency Injection in Ihrer main.dart
Datei:
import 'package:shared_preferences/shared_preferences.dart';
import 'auth_service.dart';
void setupDependencyInjection() async {
final sharedPreferences = await SharedPreferences.getInstance();
getIt.registerSingleton<SharedPreferences>(sharedPreferences);
getIt.registerSingleton<AuthService>(AuthService());
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await setupDependencyInjection();
runApp(MyApp());
}
Schritt 5: Callback-Route in Ihrer App einrichten
Fügen Sie eine Callback-Route zu Ihrem Router hinzu:
import 'package:flutter/material.dart';
import 'auth_service.dart';
class CallbackPage extends StatefulWidget {
final Map<String, String> queryParameters;
CallbackPage({this.queryParameters});
@override
_CallbackPageState createState() => _CallbackPageState();
}
class _CallbackPageState extends State<CallbackPage> {
final AuthService _authService = getIt.get<AuthService>();
@override
void initState() {
super.initState();
_handleCallback();
}
void _handleCallback() async {
await _authService.doAuthOnWeb(widget.queryParameters);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
Schritt 6: Login-Button implementieren
Implementieren Sie einen Login-Button in Ihrer App:
ElevatedButton(
onPressed: () {
final AuthService _authService = getIt.get<AuthService>();
_authService.loginAction();
},
child: Text('Anmelden'),
)
Schritt 7: Authentifizierungsstatus überwachen
Verwenden Sie StreamBuilder, um den Authentifizierungsstatus zu überwachen:
StreamBuilder<bool>(
stream: _authService.isLoggedIn,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data) {
return Text('Angemeldet');
} else {
return Text('Nicht angemeldet');
}
},
)
Schritt 8: API-Aufrufe mit Authentifizierung
Verwenden Sie den Access-Token für authentifizierte API-Aufrufe:
Future<void> callSecureApi() async {
final token = await _authService.getAccessTokenIfLoggedIn();
if (token != null) {
final response = await http.get(
Uri.parse('https://your-api.com/secure-endpoint'),
headers: {
'Authorization': 'Bearer $token',
},
);
// Verarbeiten Sie die Antwort
}
}
Vorteile dieser Implementierung
Diese OAuth2-Implementierung bietet mehrere Vorteile:
- Cross-Platform-Kompatibilität: Funktioniert sowohl in Flutter Web als auch in mobilen Apps
- Token-Persistenz: Speichert Tokens sicher für automatische Anmeldungen
- Refresh-Token-Unterstützung: Ermöglicht langfristige Authentifizierung ohne erneute Anmeldung
- Reaktive Programmierung: Verwendet RxDart für einfaches State-Management
Häufige Fehler und Lösungen
CORS-Probleme in Flutter Web
Wenn Sie CORS-Probleme in Flutter Web haben, stellen Sie sicher, dass Ihr OAuth2-Provider die richtige Origin zulässt. Bei Auth0 können Sie dies in den Anwendungseinstellungen konfigurieren.
Redirect-URI-Fehler
Stellen Sie sicher, dass die Redirect-URI in Ihrem Code exakt mit der in Ihrem OAuth2-Provider konfigurierten URI übereinstimmt.
Token-Parsing-Fehler
Wenn Sie Probleme beim Parsen des ID-Tokens haben, überprüfen Sie das Format des zurückgegebenen Tokens und passen Sie die parseIdToken
-Methode entsprechend an.
Fazit
Mit dieser Implementierung haben Sie eine robuste OAuth2-Authentifizierungslösung für Ihre Flutter-Anwendungen, die sowohl auf Web- als auch auf mobilen Plattformen funktioniert. Diese Lösung bietet eine sichere und benutzerfreundliche Anmeldeerfahrung für Ihre Nutzer. Als App-Agentur aus Köln mit Fokus auf Flutter-Entwicklung helfen wir Ihnen gerne bei der Implementierung sicherer Authentifizierungslösungen für Ihre individuellen App-Projekte. Kontaktieren Sie uns für eine Beratung zu Ihrem nächsten Projekt.