The algorithm of the application is as follows:
The user registers \ authorizes and receives a token at the output.
This token must be sent in all subsequent requests.
What I did:
void main() async{
WidgetsFlutterBinding.ensureInitialized();
final _sessionDataProvider = SessionDataProvider();
String? s = await _sessionDataProvider.getSessionId();
final HttpLink httpLink = HttpLink("https://my_api.ru/graphql/", defaultHeaders: {"Authorization": "Bearer ${s}"});
ValueNotifier<GraphQLClient> client = ValueNotifier(GraphQLClient(
link: httpLink, cache: GraphQLCache(store: InMemoryStore())));
var app = GraphQLProvider(client: client, child: MyApp());
runApp(app);
}
here I initialized GraphQl and passed the token to defaultHeaders. If it is passed in pre-written text, then everything works, but in my case I get the token after GraphQL initialization. Help how can I fix this
Use AuthLink. getToken will be called for every request.
final HttpLink httpLink = HttpLink(
'<graphql-url>',
);
final AuthLink authLink = AuthLink(
getToken: () async {
// TODO
return 'Bearer <token>';
}
);
final Link link = authLink.concat(httpLink);
Related
I want to send a GET http request with parameters, my problem is that when I add the parameters in the request URL manually it works fine, but when I pass them as parameters it returns an exception without any explanation and somehow the execution stops after Uri.https
here is the code that I want to achieve
Future<List<LawFirm>> getLawFirms () async {
Map<String, dynamic> parameters = {
'total': true
};
final uri =
Uri.http('www.vision.thefuturevision.com:5000',
'/api/law-firm', parameters);
final response = await http.get(uri);
var dynamicResponse = jsonDecode(response.body);
totaLawFirms = await dynamicResponse['total'];
var lawFirms = await dynamicResponse['data'];
List<LawFirm> list = List<LawFirm>.from(lawFirms.map((x) => LawFirm.fromJson(x)));
print(list);
notifyListeners();
return list;
}
and here is the manual way which shouldn't be applied
final response = await get(Uri.parse('$baseURL/law-firm?total=true'));
I have also tried the dio library from pub.dev but also wasn't helpful.
And finally thanks in advance to everyone
You may try this
Map<String, dynamic> parameters = {
'total': true
};
var uri = Uri(
scheme: 'http',
host: 'www.vision.thefuturevision.com:5000',
path: '/law-firm',
queryParameters: parameters,
);
final response = await http.get(uri);
import 'package:http/http.dart' as http;
final response =
await http.get(Uri.parse("${Constants.baseUrl}endpoint/param1/param2"));
Just modify your GET request like this.
Try this
import 'package:http/http.dart' as http;
callAPI() async {
String login = "sunrule";
String pwd = "api";
Uri url = Uri.parse(
"http://vijayhomeservices.in/app/api/index.php?apicall=login&login=$login&password=$pwd");
final response = await http.get(url);
if (response.statusCode == 200) {
final body = json.decode(response.body);
print(body.toString());
} else {
throw Exception("Server Error !");
}
}
Query parameters don't support bool type. Use String instead: 'true'.
A value in the map must be either a string, or an Iterable of strings, where the latter corresponds to multiple values for the same key.
Map<String, dynamic> parameters = {'total': 'true'};
final uri = Uri.http(
'www.vision.thefuturevision.com:5000', '/api/law-firm', parameters);
print(uri); // http://www.vision.thefuturevision.com:5000/api/law-firm?total=true
See Uri constructor for details.
I am retrying my api call if get 401 response but when Retrying I am ending with an following exception
following is my code for retrying multipart I had used http_interceptor package for retrying Api Calls
interceptor.dart
class AuthorizationInterceptor extends InterceptorContract {
#override
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
final prefs = await SharedPreferences.getInstance();
final extractData =
json.decode(prefs.getString('userData')!) as Map<String, dynamic>;
final Map<String, String> headers = Map.from(request.headers);
headers['Authorization'] = await extractData['accessToken'];
print(
'this is from AuthorizationInterceptor: ${extractData['accessToken']}');
// TODO: implement interceptRequest
return request.copyWith(
headers: headers,
);
}
retry.dart
class ExpiredTokenRetryPolicy extends RetryPolicy {
BuildContext context;
ExpiredTokenRetryPolicy(this.context);
#override
// TODO: implement maxRetryAttempts
int get maxRetryAttempts => 2;
#override
Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
if (response.statusCode == 401) {
print('retry token started');
//perform token refresh,get the new token and update it in the secure storage
await Provider.of<Auth>(context, listen: false).restoreAccessToken();
return true;
}
return false;
}
I am using interceptors in my widget following is my code where I am using interceptors and using retry policy
#override
Widget build(BuildContext context) {
var flutterFunctions = Provider.of<FlutterFunctions>(context);
// print('this is from insert package${token.token}');
ApiCalls repository = ApiCalls(
client: InterceptedClient.build(
retryPolicy: ExpiredTokenRetryPolicy(context),
interceptors: [
AuthorizationInterceptor(),
],
),
);
following is my restore access token method
Future<void> restoreAccessToken() async {
print('restoreAccessToken started');
//print(token);
final url = '${Ninecabsapi().urlHost}${Ninecabsapi().login}/$sessionId';
var response = await http.patch(
Uri.parse(url),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': accessToken!
},
body: json.encode(
{"refresh_token": refreshtoken},
),
);
var userDetails = json.decode(response.body);
if (response.statusCode == 401) {
print(userDetails['messages']);
}
sessionId = userDetails['data']['session_id'];
accessToken = userDetails['data']['access_token'];
accessTokenExpiryDate = DateTime.now().add(
Duration(seconds: userDetails['data']['access_token_expiry']),
);
refreshToken = userDetails['data']['refresh_token'];
refreshTokenExpiryDate = DateTime.now().add(
Duration(seconds: userDetails['data']['refresh_token_expiry']),
);
final userData = json.encode({
'sessionId': sessionId,
'refreshToken': refreshToken,
'refreshExpiry': refreshTokenExpiryDate!.toIso8601String(),
'accessToken': accessToken,
'accessTokenExpiry': accessTokenExpiryDate!.toIso8601String()
});
//print(userDetails);
notifyListeners();
final prefs = await SharedPreferences.getInstance();
prefs.setString('userData', userData);
print("this is from restoreAcessToken :$userDetails");
final extractData =
json.decode(prefs.getString('userData')!) as Map<String, dynamic>;
print('restore access token: ${extractData['accessToken']}');
reset();
}
As a rule. You must NOT write using the same Stream/MultipartFile more than once. If you need to retry sending to the same destination, you have to use a new MultipartFile each time you retry.
How to retrieve a new token with a refresh token in flutter in a ferry (graphql) client?
The response after a mutation looks like this:
{
"data": {
"auth_login": {
"access_token": "ey...",
"refresh_token": "Ua...",
"expires": 900000
}
}
}
I tried to accomplish it with fresh_graphql, but it does not work. The authenticationStatus is always unauthenticated but the token was always legit.
Implementation:
import 'dart:math';
import 'package:ferry/ferry.dart';
import 'package:ferry_hive_store/ferry_hive_store.dart';
import 'package:fresh_graphql/fresh_graphql.dart';
import 'package:gql_http_link/gql_http_link.dart';
import 'package:hive/hive.dart';
Future<Client> initClient(String? accessToken, String? refreshToken) async {
Hive.init('hive_data');
final box = await Hive.openBox<Map<String, dynamic>>('graphql');
await box.clear();
final store = HiveStore(box);
final cache = Cache(store: store);
final freshLink = await setFreshLink(accessToken ?? '', refreshToken);
final link = Link.from(
[freshLink, HttpLink('https://.../graphql/')]);
final client = Client(
link: link,
cache: cache,
);
return client;
}
Future<FreshLink> setFreshLink(String accessToken, String? refreshToken) async {
final freshLink = FreshLink<dynamic>(
tokenStorage: InMemoryTokenStorage<dynamic>(),
refreshToken: (dynamic token, client) async {
print('refreshing token!');
await Future<void>.delayed(const Duration(seconds: 1));
if (Random().nextInt(1) == 0) {
throw RevokeTokenException();
}
return OAuth2Token(
accessToken: 'top_secret_refreshed',
);
},
shouldRefresh: (_) => Random().nextInt(2) == 0,
)..authenticationStatus.listen(print);
print(freshLink.token);
print(freshLink.authenticationStatus);
await freshLink
.setToken(OAuth2Token(tokenType: 'Bearer', accessToken: accessToken));
return freshLink;
}
Any solution, even without fresh_graphql, would be appreciated!
The way I initialize my ferry client is as follows.
Create a CustomAuthLink that inherits from AuthLink.
import 'package:gql_http_link/gql_http_link.dart';
class _CustomAuthLink extends AuthLink {
_CustomAuthLink() : super(
getToken: () {
// ...
// Call your api to refresh the token and return it
// ...
String token = await ... // api refresh call
return "Bearer $token"
}
);
}
Use this custom auth link to initialise your client.
...
final link = Link.from([freshLink, HttpLink('https://.../graphql/')]);
...
Client(
link: _CustomAuthLink().concat(link),
)
...
I am not sure if you still going to need freshLink anymore. You might wanna remove it and pass HttpLink(...) directly into the .concat(...) method.
I am using this library https://pub.dev/packages/graphql_flutter for graphql in a flutter web application. Below code can be used to get authentication token:
import 'package:graphql_flutter/graphql_flutter.dart';
final HttpLink httpLink = HttpLink(
'https://api.github.com/graphql',
);
final AuthLink authLink = AuthLink(
getToken: () async => 'Bearer <YOUR_PERSONAL_ACCESS_TOKEN>',
// OR
// getToken: () => 'Bearer <YOUR_PERSONAL_ACCESS_TOKEN>',
);
but how can I put the token in the http header like x-api-key: xxxx when sending requests?
I have tried:
HttpLink link = HttpLink(
uri: 'https://api.github.com/graphql',
headers: <String, String>{
'x-api-key': 'xxxx',
},
);
but it gives me the error: The named parameter 'uri' isn't defined. Try correcting the name to an existing named parameter's name, or defining a named parameter with the name 'uri'.
Update: base on the answer from #Moaid
import 'package:graphql_flutter/graphql_flutter.dart';
typedef GetHeaders = FutureOr<Map<String, String>> Function();
class CustomAuthLink extends Link {
CustomAuthLink({
this.getHeaders,
});
final GetHeaders getHeaders;
#override
Stream<Response> request(Request request, [NextLink forward]) {
StreamController<Response> controller;
Future<void> onListen() async {
try {
final Map<String, String> headers = await getHeaders();
return request.updateContextEntry<HttpLinkHeaders>(
(_headers) => HttpLinkHeaders(
headers: <String, String>{
...headers,
},
),
);
} catch (error) {
controller.addError(error);
}
await controller.addStream(forward(request));
await controller.close();
}
controller = StreamController<Response>(onListen: onListen);
return controller.stream;
}
}
Base on the answer from #moaid-alrazhy and after checking how AuthLink is working
class CustomAuthLink extends Link {
CustomAuthLink();
#override
Stream<Response> request(Request request, [NextLink? forward]) async* {
// Some logic here
final AuthService authService = GetIt.I.get<AuthService>();
final String? token = authService.token;
final String deviceID = await DeviceInformation.deviceIMEINumber;
// TIP: do not forget getting new Request instance!
final Request req = request.updateContextEntry<HttpLinkHeaders>(
(HttpLinkHeaders? headers) => HttpLinkHeaders(
headers: <String, String>{
// put oldest headers
...headers?.headers ?? <String, String>{},
// and add a new headers
'Authorization': 'Bearer $token',
'x-device-id': deviceID,
},
),
);
// and "return" new Request with updated headers
yield* forward!(req);
}
}
Probably if you need to change only the name of the Authentication value you can edit the headerKey param
otherwise other parameters can be insert in the "defaultHeaders" fields of the HttpLink object. but I don't know if they can be use for authentication
you can add it to your HttpLink like this
HttpLink link = HttpLink(
'https://api.github.com/graphql',
headers: <String, String>{
'x-api-key': 'xxxx',
},
);
however this was in old versions .. now for more headers your have to write your own CustomAuthLink like
typedef GetHeaders = Future<Map<String, String>> Function();
class CustomAuthLink extends Link {
CustomAuthLink({
this.getHeaders,
}) : super(
request: (Operation operation, [NextLink forward]) {
StreamController<FetchResult> controller;
Future<void> onListen() async {
try {
final Map<String, String> headers = await getHeaders();
operation.setContext(<String, Map<String, String>>{
'headers': headers
});
} catch (error) {
controller.addError(error);
}
await controller.addStream(forward(operation));
await controller.close();
}
controller = StreamController<FetchResult>(onListen: onListen);
return controller.stream;
},
);
GetHeaders getHeaders;
}
I have set a login/registration for my app and currently things work with a hard coded token. However, I want to set up dynamic token upon login. Here' the code I have for now.
final AuthLink authLink = AuthLink(
// getToken: () async => 'Bearer <YOUR_PERSONAL_ACCESS_TOKEN>',
// OR
getToken: () async =>
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYwMzRkNGIyMzkwMDZhM2M4YmYxYWM2ZSIsInVzZXJuYW1lSWQiOiJCbGFjayBQYW50aGVyIiwiaWF0IjoxNjE1OTA2NzY3LCJleHAiOjE2MTY1MTE1Njd9.1xliIkkVdLkOO9AGkWwOdGhBCU18bYh26WPa4YFmEeo",
);
You can use any state management for that! For the sake of completion I'll use Riverpod
Step 1: Create a graphql_config.dart file and add this
final StateProvider<String> authTokenProvider =
StateProvider<String>((_) => '', name: 'tokenP');
final gqlClientProvider = Provider<ValueNotifier<GraphQLClient>>(
(ref) {
final String token = ref.watch(authTokenProvider).state;
final Link _link = HttpLink('YOUR_GRAPHQL_LINK', defaultHeaders: {
if (token.isNotEmpty) 'Authorization': 'Bearer $token',
});
return ValueNotifier(
GraphQLClient(
link: _link,
cache: GraphQLCache(store: HiveStore()),
),
);
},
name: 'gql Provider',
);
Step 2: Wrap your MaterialApp (main.dart file) with GraphQLProvider
class MyApp extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final client = watch(gqlClientProvider);
return GraphQLProvider(
client: client,
child: CacheProvider(
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'YOUR_APP_TITLE',
...
Step 3: Update token after successful login. Simply call the Provider and update the token
context.read(authTokenProvider).state ='YOUR_TOKEN';
PS. I didn't find a good example from the docs so I came up with this
Using the AuthLink as you did in your code sample:
final httpLink = HttpLink('https://...');
final authLink = AuthLink(getToken: () async => 'Bearer ${await ref.read(tokenProvider)}');
final link = authLink.concat(httpLink);
return GraphQLClient(
link: link,
cache: GraphQLCache(),
);
In this code, tokenProvider is a FutureProvider (Riverpod) returning the latest token.This provider is called on every request.