Flutter 앱에서 Dio interceptor로 인증 토큰 갱신 처리하기
문제 상황
회사 Flutter 앱에서 API 호출 중 토큰 만료로 인한 401 에러가 빈번하게 발생했다. 매번 로그인 화면으로 보내는 것은 UX상 좋지 않아서, refresh token을 이용한 자동 갱신 로직이 필요했다.
Dio Interceptor 구현
Dio의 interceptor를 사용하면 요청/응답을 가로채서 처리할 수 있다.
class AuthInterceptor extends Interceptor {
final Dio _dio;
final TokenRepository _tokenRepo;
AuthInterceptor(this._dio, this._tokenRepo);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final token = await _tokenRepo.getAccessToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
try {
final refreshToken = await _tokenRepo.getRefreshToken();
final response = await _dio.post('/auth/refresh',
data: {'refreshToken': refreshToken},
options: Options(headers: {'requiresAuth': false})
);
await _tokenRepo.saveTokens(
response.data['accessToken'],
response.data['refreshToken']
);
// 원래 요청 재시도
final opts = err.requestOptions;
opts.headers['Authorization'] = 'Bearer ${response.data['accessToken']}';
final cloneReq = await _dio.fetch(opts);
return handler.resolve(cloneReq);
} catch (e) {
// refresh 실패 시 로그아웃
await _tokenRepo.clearTokens();
return handler.reject(err);
}
}
handler.next(err);
}
}
주의할 점
토큰 갱신 API 호출 시 무한 루프를 방지하기 위해 해당 요청은 interceptor를 거치지 않도록 처리해야 한다. options에 플래그를 추가하거나 별도의 Dio 인스턴스를 사용하는 방법이 있다.
또한 동시에 여러 API 호출이 401을 받았을 때 토큰 갱신이 중복 실행되지 않도록 Lock 메커니즘을 추가하는 것도 고려할 필요가 있다.
결과
사용자가 앱을 사용하는 도중 토큰 만료로 인한 인증 오류를 느끼지 못하게 되었다. Refresh token마저 만료된 경우에만 로그인 화면으로 이동하므로 UX가 개선되었다.