forked from ml-opensource/flutter-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(ml-opensource#80): Introduce new
ApiAuthInterceptor
- Loading branch information
Showing
2 changed files
with
117 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import 'package:flutter_template/data/api/new/interceptor/api_auth_interceptor.dart'; | ||
|
||
/// An exception indicating that token was invalid even after reauthentication. | ||
/// See [ApiAuthInterceptor]. | ||
class TokenUnauthorizedException implements Exception { | ||
final String message; | ||
|
||
const TokenUnauthorizedException([this.message = '']); | ||
|
||
@override | ||
String toString() { | ||
return '${runtimeType.toString()}: $message'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import 'package:dio/dio.dart'; | ||
import 'package:flutter_template/data/api/exceptions/token_unauthorized_exception.dart'; | ||
import 'package:flutter_template/data/api/new/authenticator/authenticator.dart'; | ||
import 'package:flutter_template/injection/network_module.dart'; | ||
import 'package:injectable/injectable.dart'; | ||
|
||
@injectable | ||
class ApiAuthInterceptor extends QueuedInterceptor { | ||
ApiAuthInterceptor( | ||
@Named(dioAuth) this._dio, | ||
this._authenticator, | ||
); | ||
|
||
final Dio _dio; | ||
final Authenticator _authenticator; | ||
|
||
void _setAuthHeader(RequestOptions options) { | ||
final accessToken = _authenticator.accessToken; | ||
|
||
if (accessToken != null) { | ||
options.headers['Authorization'] = 'Bearer $accessToken'; | ||
} | ||
} | ||
|
||
Future<void> _reauthenticateIfNeeded(RequestOptions options) async { | ||
if (_authenticator.expiresSoon) { | ||
await _authenticator.reauthenticate(options); | ||
} | ||
} | ||
|
||
@override | ||
Future<void> onRequest( | ||
RequestOptions options, | ||
RequestInterceptorHandler handler, | ||
) async { | ||
try { | ||
await _reauthenticateIfNeeded(options); | ||
} on Exception catch (e) { | ||
handler.reject(DioError(requestOptions: options, error: e)); | ||
return; | ||
} | ||
|
||
_setAuthHeader(options); | ||
|
||
handler.next(options); | ||
} | ||
|
||
@override | ||
Future<void> onError( | ||
DioError err, | ||
ErrorInterceptorHandler handler, | ||
) async { | ||
// The error is not a 401 error — so nothing to handle in this interceptor. | ||
// Let other interceptors work. | ||
if (err.response?.statusCode != 401) { | ||
handler.next(err); | ||
|
||
return; | ||
} | ||
|
||
try { | ||
await _authenticator.reauthenticate(err.requestOptions); | ||
} on Exception catch (e) { | ||
handler.reject( | ||
DioError( | ||
requestOptions: err.requestOptions, | ||
error: e, | ||
response: err.response, | ||
), | ||
); | ||
|
||
return; | ||
} | ||
|
||
final options = err.requestOptions; | ||
|
||
_setAuthHeader(options); | ||
|
||
// Try to redo the request using the new auth token | ||
|
||
try { | ||
final response = await _dio.fetch(options); | ||
handler.resolve(response); | ||
} on DioError catch (e) { | ||
// As we've tried to handle the 401 error, seeing this error again | ||
// means that even with the new token we're unable to proceed any further. | ||
// We must log the user out. | ||
if (e.response?.statusCode == 401) { | ||
final error = DioError( | ||
requestOptions: options, | ||
error: const TokenUnauthorizedException(), | ||
response: e.response, | ||
); | ||
|
||
handler.reject(error); | ||
} else { | ||
// In case of other errors — let other interceptors work. | ||
|
||
handler.next(e); | ||
} | ||
} | ||
} | ||
} |