Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: example queued_interceptor_csrftoken.dart #2128

Closed
wants to merge 3 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 75 additions & 72 deletions example/lib/queued_interceptor_crsftoken.dart
Original file line number Diff line number Diff line change
@@ -1,92 +1,95 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add the main idea and the overall flow at the top with comments?

Copy link
Contributor Author

@seunghwanly seunghwanly Mar 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I added key features and a scenario too

import 'package:dio/dio.dart';

/// Used for Web Cookie
// const String _cookieKey = 'XSRF_TOKEN';
const String _headerKey = 'X-Csrf-Token';

void main() async {
final dio = Dio();
// dio instance to request token
final tokenDio = Dio();
String? csrfToken;
dio.options.baseUrl = 'https://seunghwanlytest.mocklab.io/';
tokenDio.options = dio.options;
String? cachedCSRFToken;

final dio = Dio()
..options.baseUrl = 'https://httpbin.org/'
..interceptors.add(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use httpbun.com instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, changed baseUrl and used mix 👍

QueuedInterceptorsWrapper(
/// Add CSRF token to headers, if it exists
onRequest: (requestOptions, handler) {
if (cachedCSRFToken != null) {
requestOptions.headers[_headerKey] = cachedCSRFToken;
}
return handler.next(requestOptions);
},

/// Update CSRF token from [response] headers, if it exists
onResponse: (response, handler) {
final token = response.headers.value(_headerKey);
if (token != null) {
cachedCSRFToken = token;
}
return handler.resolve(response);
},
),
);

/// Make Requests
await dio.get('/anything');
log('1) Called \'/anything\' CSRF Token: $cachedCSRFToken');

/// Expected 401 Exception
try {
await dio.post('/status/401');
} catch (e) {
log('2) Called \'/status/401\' CSRF Token: $cachedCSRFToken');
}

/// Add `onError` interceptor to request new CSRF token
dio.interceptors.add(
QueuedInterceptorsWrapper(
onRequest: (options, handler) async {
print('send request:path:${options.path},baseURL:${options.baseUrl}');
/// Request new CSRF token
/// if the response status code is `401`
onError: (error, handler) async {
log('Error Detected: ${error.message}');

if (csrfToken == null) {
print('no token,request token firstly...');
if (error.response == null) return handler.next(error);

final result = await tokenDio.get('/token');
if (error.response?.statusCode == 401) {
try {
final tokenDio = Dio(
BaseOptions(baseUrl: error.requestOptions.baseUrl),
);

if (result.statusCode != null && result.statusCode! ~/ 100 == 2) {
/// assume `token` is in response body
final body = jsonDecode(result.data) as Map<String, dynamic>?;
/// Generate CSRF token
///
/// This is a MOCK REQUEST to generate a CSRF token.
/// In a real-world scenario, this should be generated by the server.
final result = await tokenDio.post(
'/response-headers',
queryParameters: {
_headerKey: '94d6d1ca-fa06-468f-a25c-2f769d04c26c',
},
);

if (body != null && body.containsKey('data')) {
options.headers['csrfToken'] = csrfToken = body['data']['token'];
print('request token succeed, value: $csrfToken');
print(
'continue to perform request:path:${options.path},baseURL:${options.path}',
);
return handler.next(options);
if (result.statusCode == null || result.statusCode! ~/ 100 != 2) {
throw DioException(requestOptions: result.requestOptions);
}
}

return handler.reject(
DioException(requestOptions: result.requestOptions),
true,
);
}
final updatedToken = result.headers.value(_headerKey);
if (updatedToken == null) throw ArgumentError.notNull(_headerKey);

options.headers['csrfToken'] = csrfToken;
return handler.next(options);
},
onError: (error, handler) async {
/// Assume 401 stands for token expired
if (error.response?.statusCode == 401) {
print('the token has expired, need to receive new token');
final options = error.response!.requestOptions;

/// assume receiving the token has no errors
/// to check `null-safety` and error handling
/// please check inside the [onRequest] closure
final tokenResult = await tokenDio.get('/token');

/// update [csrfToken]
/// assume `token` is in response body
final body = jsonDecode(tokenResult.data) as Map<String, dynamic>?;
options.headers['csrfToken'] = csrfToken = body!['data']['token'];

if (options.headers['csrfToken'] != null) {
print('the token has been updated');

/// since the api has no state, force to pass the 401 error
/// by adding query parameter
final originResult = await dio.fetch(options..path += '&pass=true');
if (originResult.statusCode != null &&
originResult.statusCode! ~/ 100 == 2) {
return handler.resolve(originResult);
}
cachedCSRFToken = updatedToken;

return handler.resolve(error.response!);
} on DioException catch (e) {
return handler.reject(e);
}
print('the token has not been updated');
return handler.reject(
DioException(requestOptions: options),
);
}
return handler.next(error);
},
),
);
await dio.post('/status/401');
log('3) Called \'/status/401\' CSRF Token: $cachedCSRFToken');

FutureOr<void> onResult(d) {
print('request ok!');
}

/// assume `/test?tag=2` path occurs the authorization error (401)
/// and token to be updated
await dio.get('/test?tag=1').then(onResult);
await dio.get('/test?tag=2').then(onResult);
await dio.get('/test?tag=3').then(onResult);
await dio.get('/anything/posts');
log('4) Called \'/anything/posts\' CSRF Token: $cachedCSRFToken');
}
Loading