import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; import 'dart:convert' show jsonDecode; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; class User { const User( {required this.firstname, required this.lastname, required this.email}); final String firstname; final String lastname; final String email; static User fromJson(jsonDecode) { if (jsonDecode == "") { return const User(firstname: '', lastname: '', email: ''); } return User( firstname: jsonDecode['firstname'], lastname: jsonDecode['lastname'], email: jsonDecode['email']); } Map toJson() => { 'firstname': firstname, 'lastname': lastname, 'email': email, }; } class TokenRefresher { final AuthService authService; final Duration refreshInterval; late Timer _timer; TokenRefresher(this.authService, {required this.refreshInterval}); void start() { _timer = Timer.periodic(refreshInterval, (timer) async { try { await authService.doRefreshToken(); } catch (e) { if (kDebugMode) { print('Failed to refresh token'); } } }); } void stop() { _timer.cancel(); } } class AuthService { String clientId; String host; String redirectUri; String realm; String callbackUrlScheme; User? user; TokenRefresher? tokenRefresher; String? accessToken = ''; String? refreshToken = ''; AuthService( {required this.host, required this.realm, required this.clientId, required this.redirectUri, required this.callbackUrlScheme}) ; // Add init method to initialize the AuthService, it retrives the access token from the shared preferences Future init() async { final prefs = await SharedPreferences.getInstance(); accessToken = prefs.getString('accessToken'); refreshToken = prefs.getString('refreshToken'); } Future login() async { // Build the url final url = Uri.https(host, '/auth/realms/$realm/protocol/openid-connect/auth', { 'response_type': 'code', 'client_id': clientId, 'redirect_uri': '$callbackUrlScheme:/$redirectUri', 'scope': 'openid', }); // Present the dialog to the user try { final result = await FlutterWebAuth2.authenticate( url: url.toString(), callbackUrlScheme: callbackUrlScheme); // Extract code from resulting url final code = Uri.parse(result).queryParameters['code']; // Use this code to get an access token final tokenUrl = Uri.https(host, '/auth/realms/$realm/protocol/openid-connect/token'); var response = await http.post(tokenUrl, body: { 'client_id': clientId, 'grant_type': 'authorization_code', 'code': code, 'redirect_uri': '$callbackUrlScheme:/$redirectUri', }); if (response.statusCode != 200) { throw Exception('Failed to login'); } // Get the access token from the response await setAccessToken(jsonDecode(response.body)['access_token'] as String); await setRefreshToken(jsonDecode(response.body)['refresh_token'] as String); } catch (e) { return false; } tokenRefresher = TokenRefresher(this, refreshInterval: const Duration(seconds: 60)); tokenRefresher!.start(); return true; } Future logout() async { if (accessToken == null || accessToken == "") { throw Exception('Not logged in'); } final logoutUrl = Uri.https(host, '/auth/realms/$realm/protocol/openid-connect/logout'); final response = await http.post(logoutUrl, body: { 'client_id': clientId, 'refresh_token': refreshToken, }); if (response.statusCode != 204) { throw Exception('Failed to logout'); } final prefs = await SharedPreferences.getInstance(); await prefs.clear(); user = null; tokenRefresher!.stop(); return true; } Future doRefreshToken() async { if (refreshToken == "") { throw Exception('Not logged in'); } final tokenUrl = Uri.https(host, '/auth/realms/$realm/protocol/openid-connect/token'); final response = await http.post(tokenUrl, body: { 'client_id': clientId, 'grant_type': 'refresh_token', 'refresh_token': refreshToken, }); if (response.statusCode != 200) { throw Exception('Failed to refresh token: ${response.body}'); } // Get the access token from the response await setAccessToken(jsonDecode(response.body)['access_token'] as String); await setRefreshToken(jsonDecode(response.body)['refresh_token'] as String); return true; } Future isLoggedIn() async { user = await getUser(); return user != null; } Future setAccessToken(String token) async { final prefs = await SharedPreferences.getInstance(); accessToken = token; } Future setRefreshToken(String token) async { final prefs = await SharedPreferences.getInstance(); prefs.setString('refreshToken', token); refreshToken = token; } Future getUser() async { if (this.user != null) { return this.user!; } final userInfoEndpoint = Uri.https(host, '/auth/realms/$realm/protocol/openid-connect/userinfo'); var response = await http.get(userInfoEndpoint, headers: { 'Authorization': 'Bearer $accessToken', }); if (response.statusCode != 200) { return null; } final userMap = jsonDecode(response.body) as Map; var user = User( firstname: userMap['given_name'] as String, lastname: userMap['family_name'] as String, email: userMap['email'] as String); this.user = user; return user; } }