개요
- Dio를 사용하면 인터셉터를 쉽게 구현할 수 있음
- 인터셉터에서 401 응답에 따라 리프레시 토큰을 사용하는 방식
- 본 예제에서 리프레시 토큰은 클라이언트가 직접 다루지 않고 httpOnly 쿠키로 브라우저에 저장되었다고 가정
구현 예시
- 클라이언트가 브라우저 환경에서 httpOnly 쿠키를 서버에 보낼 수 있도록 하려면
withCredentials: true옵션 필요
!! 서버에서도 CORS 및 credentials 설정 필요
class AuthClient {
final Dio _dio;
final AuthService authService;
AuthClient({
required this.authService,
}) : _dio = Dio(
BaseOptions(
baseUrl: 'your_api_url',
extra: {
if (kIsWeb) 'withCredentials': true,
},
),
);
// ..
Future<Result<dynamic, AuthError>> refreshToken() async {
try {
final response = await _dio.get('/auth/refresh');
final newToken = response.data;
authService.refresh(newToken);
return Result.ok(newToken);
} catch (e) {
authService.signout();
debugPrint(e.toString());
return Result.error(AuthError.refreshError);
}
}
}
AuthService는 토큰을 저장하고 관련된 이벤트를 발행하는 서비스라고 가정
AuthInterceptor에서 onError 처리
class AuthInterceptor extends Interceptor {
final TokenProvider tokenProvider;
AuthInterceptor({
required this.tokenProvider,
});
// ...
@override
void onError(
DioException err,
ErrorInterceptorHandler handler,
) async {
final token = await tokenProvider.getToken();
if (token != null && err.response?.statusCode == 401) {
final result = await authClient.refreshToken();
switch (result) {
case Success<dynamic, AuthError>(:final data):
final retryRequest = err.requestOptions
..headers['Authorization'] = 'Bearer $data';
final response = await Dio().fetch(retryRequest);
return handler.resolve(response);
case Failure<dynamic, AuthError>(:final error):
return handler.next(
DioException(
requestOptions: err.requestOptions,
error: error,
),
);
}
}
return handler.next(err);
}
}
저장된 토큰이 있는데 401 응답인 경우에 토큰 요청
인터셉터 주입 후 추가
class DioApiClient implements ApiClient {
final Dio _dio;
DioApiClient({
required Interceptor authInterceptor,
}) : _dio = Dio()
..interceptors.add(authInterceptor)
..interceptors.add(otherInterceptor);
// ..
}
참고
- ChatGPT 4