Skip to content

Commit

Permalink
Merge pull request #122 from SocialGouv/tech/181_tokens_in_secured_st…
Browse files Browse the repository at this point in the history
…orage

[Tech] 181 - tokens in secured storage
  • Loading branch information
Bonnet Maxime authored Dec 27, 2021
2 parents 6949232 + 03f5c16 commit d609c70
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 92 deletions.
4 changes: 3 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
android:label="@string/app_name"
android:fullBackupContent="@xml/backup_rules">
>
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
Expand Down
4 changes: 4 additions & 0 deletions android/app/src/main/res/xml/backup_rules.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="FlutterSecureStorage"/>
</full-backup-content>
6 changes: 3 additions & 3 deletions lib/auth/auth_access_token_retriever.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ class AuthAccessTokenRetriever {
AuthAccessTokenRetriever(this._authenticator);

Future<String> accessToken() async {
final idToken = _authenticator.idToken();
final idToken = await _authenticator.idToken();
if (idToken == null) throw Exception("ID Token is null");
if (idToken.isValid()) return _authenticator.accessToken()!;
if (idToken.isValid()) return (await _authenticator.accessToken())!;
final refreshTokenStatus = await _authenticator.refreshToken();
switch (refreshTokenStatus) {
case RefreshTokenStatus.SUCCESSFUL:
return _authenticator.accessToken()!;
return (await _authenticator.accessToken())!;
case RefreshTokenStatus.EXPIRED_REFRESH_TOKEN:
_store.dispatch(RequestLogoutAction(LogoutRequester.SYSTEM));
throw Exception("ID Token is null");
Expand Down
28 changes: 14 additions & 14 deletions lib/auth/authenticator.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:pass_emploi_app/auth/auth_id_token.dart';
import 'package:pass_emploi_app/auth/auth_token_request.dart';
import 'package:pass_emploi_app/auth/auth_token_response.dart';
import 'package:pass_emploi_app/configuration/configuration.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'auth_logout_request.dart';
import 'auth_refresh_token_request.dart';
Expand All @@ -21,7 +21,7 @@ const Map<String, String> similoAdditionalParameters = {"kc_idp_hint": "similo-j
class Authenticator {
final AuthWrapper _authWrapper;
final Configuration _configuration;
final SharedPreferences _preferences;
final FlutterSecureStorage _preferences;

Authenticator(this._authWrapper, this._configuration, this._preferences);

Expand All @@ -44,18 +44,18 @@ class Authenticator {
}
}

bool isLoggedIn() => _preferences.containsKey(_idTokenKey);
Future<bool> isLoggedIn() async => await _preferences.read(key: _idTokenKey) != null;

AuthIdToken? idToken() {
final String? idToken = _preferences.getString(_idTokenKey);
Future<AuthIdToken?> idToken() async {
final String? idToken = await _preferences.read(key: _idTokenKey);
if (idToken != null) return AuthIdToken.parse(idToken);
return null;
}

String? accessToken() => _preferences.getString(_accessTokenKey);
Future<String?> accessToken() async => _preferences.read(key: _accessTokenKey);

Future<RefreshTokenStatus> refreshToken() async {
final String? refreshToken = _preferences.getString(_refreshTokenKey);
final String? refreshToken = await _preferences.read(key: _refreshTokenKey);
if (refreshToken == null) return RefreshTokenStatus.USER_NOT_LOGGED_IN;

try {
Expand All @@ -81,7 +81,7 @@ class Authenticator {
}

Future<bool> logout() async {
final String? idToken = _preferences.getString(_idTokenKey);
final String? idToken = await _preferences.read(key: _idTokenKey);
if (idToken == null) return false;
try {
await _authWrapper.logout(AuthLogoutRequest(
Expand All @@ -97,14 +97,14 @@ class Authenticator {
}

void _saveToken(AuthTokenResponse response) {
_preferences.setString(_idTokenKey, response.idToken);
_preferences.setString(_accessTokenKey, response.accessToken);
_preferences.setString(_refreshTokenKey, response.refreshToken);
_preferences.write(key: _idTokenKey, value: response.idToken);
_preferences.write(key: _accessTokenKey, value: response.accessToken);
_preferences.write(key: _refreshTokenKey, value: response.refreshToken);
}

void _deleteToken() {
_preferences.remove(_idTokenKey);
_preferences.remove(_accessTokenKey);
_preferences.remove(_refreshTokenKey);
_preferences.delete(key: _idTokenKey);
_preferences.delete(key: _accessTokenKey);
_preferences.delete(key: _refreshTokenKey);
}
}
6 changes: 3 additions & 3 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http_interceptor/http/intercepted_client.dart';
import 'package:matomo/matomo.dart';
import 'package:package_info/package_info.dart';
Expand Down Expand Up @@ -35,7 +36,6 @@ import 'package:pass_emploi_app/repositories/rendezvous_repository.dart';
import 'package:pass_emploi_app/repositories/search_location_repository.dart';
import 'package:pass_emploi_app/repositories/user_action_repository.dart';
import 'package:redux/redux.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'configuration/app_version_checker.dart';
import 'configuration/configuration.dart';
Expand Down Expand Up @@ -98,8 +98,8 @@ Future<Store<AppState>> _initializeReduxStore(
PushNotificationManager pushNotificationManager,
) async {
final headersBuilder = HeadersBuilder();
final preferences = await SharedPreferences.getInstance();
final authenticator = Authenticator(AuthWrapper(FlutterAppAuth()), configuration, preferences);
final securedPreferences = FlutterSecureStorage(aOptions: AndroidOptions(encryptedSharedPreferences: true));
final authenticator = Authenticator(AuthWrapper(FlutterAppAuth()), configuration, securedPreferences);
final accessTokenRetriever = AuthAccessTokenRetriever(authenticator);
final httpClient = InterceptedClient.build(
interceptors: [AccessTokenInterceptor(accessTokenRetriever), LoggingInterceptor()],
Expand Down
6 changes: 3 additions & 3 deletions lib/redux/middlewares/login_middleware.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class LoginMiddleware extends MiddlewareClass<AppState> {
}

void _checkIfUserIsLoggedIn(Store<AppState> store) async {
if (_authenticator.isLoggedIn()) {
if (await _authenticator.isLoggedIn()) {
_dispatchLoginSuccess(store);
} else {
store.dispatch(NotLoggedInAction());
Expand All @@ -49,8 +49,8 @@ class LoginMiddleware extends MiddlewareClass<AppState> {
}
}

void _dispatchLoginSuccess(Store<AppState> store) {
final AuthIdToken idToken = _authenticator.idToken()!;
void _dispatchLoginSuccess(Store<AppState> store) async {
final AuthIdToken idToken = (await _authenticator.idToken())!;
final user = User(id: idToken.userId, firstName: idToken.firstName, lastName: idToken.lastName);
store.dispatch(LoginAction.success(user));
}
Expand Down
42 changes: 42 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,48 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.2"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.2"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
flutter_svg:
dependency: "direct main"
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependencies:
google_fonts: 2.1.0
# Analytics
matomo: 1.1.0
flutter_secure_storage: 5.0.2

dev_dependencies:
flutter_test:
Expand Down
8 changes: 4 additions & 4 deletions test/auth/auth_access_token_retriever_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,15 @@ class AuthenticatorLoggedInAndValidIdTokenStub extends Authenticator {
AuthenticatorLoggedInAndValidIdTokenStub() : super(DummyAuthWrapper(), configuration(), SharedPreferencesSpy());

@override
AuthIdToken? idToken() => AuthIdToken(
Future<AuthIdToken?> idToken() async => AuthIdToken(
userId: "id",
firstName: "F",
lastName: "L",
expiresAt: (DateTime.now().millisecondsSinceEpoch ~/ 1000) + 1000,
);

@override
String? accessToken() => "Access token";
Future<String?> accessToken() async => "Access token";
}

class AuthenticatorLoggedInAndInvalidIdTokenStub extends Authenticator {
Expand All @@ -111,10 +111,10 @@ class AuthenticatorLoggedInAndInvalidIdTokenStub extends Authenticator {
: super(DummyAuthWrapper(), configuration(), SharedPreferencesSpy());

@override
AuthIdToken? idToken() => AuthIdToken(userId: "id", firstName: "F", lastName: "L", expiresAt: 0);
Future<AuthIdToken?> idToken() async => AuthIdToken(userId: "id", firstName: "F", lastName: "L", expiresAt: 0);

@override
String? accessToken() => "Access token";
Future<String?> accessToken() async => "Access token";

@override
Future<RefreshTokenStatus> refreshToken() => Future.value(refreshTokenStatus);
Expand Down
18 changes: 9 additions & 9 deletions test/auth/authenticator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ void main() {
await authenticator.login(AuthenticationMode.GENERIC);

// Then
expect(authenticator.isLoggedIn(), true);
expect(await authenticator.isLoggedIn(), true);
});

test('isLoggedIn is FALSE when login failed', () async {
Expand All @@ -83,7 +83,7 @@ void main() {
await authenticator.login(AuthenticationMode.GENERIC);

// Then
expect(authenticator.isLoggedIn(), false);
expect(await authenticator.isLoggedIn(), false);
});
});

Expand Down Expand Up @@ -136,7 +136,7 @@ void main() {

// Then
expect(result, RefreshTokenStatus.EXPIRED_REFRESH_TOKEN);
expect(authenticator.isLoggedIn(), false);
expect(await authenticator.isLoggedIn(), false);
});

test('refresh token returns GENERIC_ERROR when user is logged in but refresh token fails on generic exception',
Expand All @@ -162,7 +162,7 @@ void main() {

// Then
expect(result, RefreshTokenStatus.USER_NOT_LOGGED_IN);
expect(authenticator.isLoggedIn(), false);
expect(await authenticator.isLoggedIn(), false);
});
});

Expand All @@ -178,7 +178,7 @@ void main() {

// Then
expect(result, isTrue);
expect(authenticator.isLoggedIn(), false);
expect(await authenticator.isLoggedIn(), false);
});

test('FALSE is returned if user was logged in but logout fails', () async {
Expand Down Expand Up @@ -219,7 +219,7 @@ void main() {
await authenticator.login(AuthenticationMode.GENERIC);

// When
final token = authenticator.idToken();
final token = await authenticator.idToken();

// Then
expect(
Expand All @@ -238,7 +238,7 @@ void main() {
await authenticator.login(AuthenticationMode.GENERIC);

// When
final token = authenticator.idToken();
final token = await authenticator.idToken();

// Then
expect(token, isNull);
Expand All @@ -250,7 +250,7 @@ void main() {
await authenticator.login(AuthenticationMode.GENERIC);

// When
final token = authenticator.accessToken();
final token = await authenticator.accessToken();

// Then
expect(token, "accessToken");
Expand All @@ -262,7 +262,7 @@ void main() {
await authenticator.login(AuthenticationMode.GENERIC);

// When
final token = authenticator.accessToken();
final token = await authenticator.accessToken();

// Then
expect(token, isNull);
Expand Down
Loading

0 comments on commit d609c70

Please sign in to comment.