Background
I'm developing a database manager via http connection in Rust. The database has a authenticaition-expiration strategy, and I need to persist the connection in my database manager by re-login after the expiration time.
Pseudocode
My database manager is Database, and there are some methods of it:
struct Database {
access_token: Option<String>
}
impl Database {
async fn login(&mut self, auth: Option<String>) {
if let Some(auth) = auth {
// If user provide auth, login with auth
let response = do_log_in(auth).await;
// get access_token from response
self.access_token = Some(response.access_token);
} else {
// If user doesn't provide auth, login with access_token
do_log_in(access_token).await;
}
restart_timer();
}
// restart the login timer
fn restart_timer(&mut self) {
self.cancel_timer();
self.do_start_timer();
}
}
My implementation
I use tokio to implement the restart_timer thing:
First, I add a field timer: Option<tokio::task::JoinHandle> to Database
Then, when do_start_timer() is called, use tokio::spawn to spawn a new task and assign the returning join handle to self.timer.
Inside the tokio::spawn closure, I await the tokio::time::delay_for, then await the login.
To cancel the timer, I just assign self.timer with None, making the task detached.
Problem
In the detail code of the above implementation, there is a touch thing:
self.timer = Some(tokio::spawn(async move {
tokio::time::delay_for(/* ... */).await;
self.login().await;
}));
The above snippet is in the do_start_timer thing, but this can't compile:
expected `&mut Database`
found `&mut Database`
but, the lifetime must be valid for the static
I don't know what to do.
Related
I have this scenario: Xamarin.Forms App connected with Web Api 2. I make all requests and get the data i want. Now when the session token expires, i need to refresh the token but don't logout the user. The user don't need to know when token is refreshed. How to organize this, add in every request if statement when i send it and check if token expires.
This is one of my requests:
public async Task<User> GetProfileSetup()
{
try
{
if (CrossConnectivity.Current.IsConnected)
{
string token = DependencyService.Get<ISharedFunctions>().GetAccessToken();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetAsync(#"api/Profile/GetProfilSetup");
if (response.IsSuccessStatusCode)
{
string jsonMessage;
using (Stream responseStream = await response.Content.ReadAsStreamAsync())
{
jsonMessage = new StreamReader(responseStream).ReadToEnd();
}
User user = JsonConvert.DeserializeObject<User>(jsonMessage);
return user;
}
else
{
var m = response.Content.ToString();
return null;
}
}
else
{
return null;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
string error = ex.Message;
return null;
}
}
P.S I have Methods for GetToken and RefreshToken in my Api and they are working, just how to organize Refreshing ?
It really depends on what libraries are you using on your project.
But let's say you're using plain c# to handled your HTTP calls.
[OPTION 1] Polly
I can recommend you looking at Polly
It's a great library with a lot of features. You can use the Retry policy to handled expired tokens:
var _unauthorizedPolicy = Policy
.Handle<Exception>(ex => ex.StatusCode == HttpStatusCode.Unauthorized) // check here for your exception to be the right one
.RetryAsync(3, async (exception, retryCount, context) =>
{
try
{
var token = await _authService.RefreshToken();
// save the new token or whatever you need to store it
}
catch (Exception ex)
{
// RefreshToken failed, you should probably sign out the user
SignOut();
}
});
What this does is that Polly will try to execute your normal HTTP call and in case it fails and the cause is specified in Handle, then a retry mechanism is fired that will try to refresh the token and then retry your request. In the end, in case the token cannot be refreshed, you sign out the user. Of course, all this can be customized, check Polly's documentation is pretty well written.
Please note that inside Handle<T> you must put the right exception. I just used Exception as a placeholder since I'm not sure what Exception is thrown in your case.
Then you would call your method with this policy:
var result = await _unauthorizedPolicy.ExecuteAsync(() => GetProfileSetup())
And you can reuse that policy for any call, no need to create it every time.
[OPTION 2] DelegatingHandler
I will like here another StackOverflow answer:
How to Refresh a token using IHttpClientFactory
Basically you can intercept every HTTP call made via a HttpClient and refresh/add a token to your requests.
Note that that answer does not obligate you to use IHttpClientFactory, it also works for a simple HttpClient.
Also a little bit off-topic. You might want to look up for libraries to handle htt calls such as Retrofit. It will really reduce the amount of boilerplate code.
I am working on a simple Lambda function and I was wondering if I could pass in client (dynamodb this time) to the handler, so we do not re-connect for every request.
The macro is defined here:
https://docs.rs/lambda_http/0.1.1/lambda_http/macro.lambda.html 3
My function so far:
fn main() -> Result<(), Box<dyn Error>> {
simple_logger::init_with_level(log::Level::Debug)?;
info!("Starting up...");
let dynamodb_client = DynamoDbClient::new(Region::EuCentral1);
lambda!(router);
return Ok(());
}
fn router(req: Request, ctx: Context) -> Result<impl IntoResponse, HandlerError> {
let h_req = HReq {
http_path: req.uri().path(),
http_method: req.method(),
};
match h_req {
HReq {
http_path: "/login",
http_method: &Method::POST,
} => user_login(req, ctx),
_ => {
error!(
"Not supported http method or path {}, {}",
h_req.http_path, h_req.http_method
);
let mut resp = Response::default();
*resp.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
Ok(resp)
}
}
}
Is it possible to extend this macro to have a second option so I can add the client all the way down to the functions which are actually talking to the database?
DynamoDB is a web service, each request to it is treated as a distinct API call.
There is no functionality to keep a client connection alive in the same way you would with a regular database connection (e.g. MySQL).
My rust knowledge is a little lacking, so I don't know if http keepalive is set by default with the DynamoDBClient, but making sure http keepalive is set will help performance.
After considering all the options I decided to implement this with lazy_static.
#[macro_use]
extern crate lazy_static;
lazy_static! {
static ref DYNAMODB_CLIENT: DynamoDbClient = DynamoDbClient::new(Region::EuCentral1);
}
This is getting instantiated at run time and can be used internally in the module without any problems.
I want to display an error screen if net is not there. I am not using connectivity package because I don't want continuous check. I just want to handle exception while calling backend api and display the screen. I am unable to catch the exception.
I found this issue and this question about socket exceptions but none seem to help me.
This is how I call my backend api -
callBackendApi() async {
try {
http.Response response = await Future.value(/*api call here*/)
.timeout(Duration(seconds: 90), onTimeout: () {
print('TIME OUT HAPPENED');
});
} catch (exception) {
Fluttertoast.showToast(msg: 'Check internet connection.');
print('Error occurred' + exception.toString());
}
}
I use dio like this:
try {
var formData = FormData.from(Map<String, dynamic>.from(data));
var response = await dio.post(
uri,
data: formData,
);
jsonResponse = json.decode(response.data);
} on DioError catch (e) {
if (DioErrorType.RECEIVE_TIMEOUT == e.type ||
DioErrorType.CONNECT_TIMEOUT == e.type) {
throw CommunicationTimeoutException(
"Server is not reachable. Please verify your internet connection and try again");
} else if (DioErrorType.RESPONSE == e.type) {
// 4xx 5xx response
// throw exception...
} else if (DioErrorType.DEFAULT == e.type) {
if (e.message.contains('SocketException')) {
throw CommunicationTimeoutException('blabla');
}
} else {
throw CommunicationException("Problem connecting to the server. Please try again.");
}
}
My solution is to import 'dart.io' in order to catch SocketException from try block:
import 'package:http/http.dart' as http;
import 'dart:io';
try{
//Handle you network call code block in here
}on SocketException catch(_){
//To handle Socket Exception in case network connection is not available during initiating your network call
}
Well i don't know if my answer will be solving your question but days ago i had a problem little bit likely yours but in my case was using firebase realtime database. I was asking to myself how can i protect my app from network fails like no internet connection available? Well i am not using connectivity package too so i solve this problem with an approach that you already has been try using a timeout for network operations. I will share two snipets with differents approaches that i had implemented to handle this kind of problem adding some comments trying explain the differences between them.
Approach 1 - Setting timeout outside from network request method
Well the snipet below is a simple firebase database request where _viewsRef is a DatabaseReference and the once method do the request and returns me a Future with or without data.
// get users visualization from realtime database and returns a future
static Future<DataSnapshot> getUserVisualizations({#required String uid}) async {
return _viewsRef.child(uid).limitToLast(50).once();
}
In my BLoC component class i am calling the method below and setting a timeout to the future that is returned.
myBlocComponentMethod(){
//.. some work and finally the call
FirebaseUserViewsHelper.getUserVisualizations(uid: _currentUid)
.then(
(dataSnapshot){
if (dataSnapshot.value == null) {
// do some things to handle no data
}
else {
/// handle your data here
});
}
} // setting timeout here is an important point
).timeout( Duration(seconds: Constants.NETWORK_TIMEOUT_SECONDS),
onTimeout: (){
// method to handle a timeout exception and tell to view layer that
// network operation fails
// if we do not implement onTimeout callback the framework will throw a TimeoutException
} );
}
Well what is the point here? In this case if the timeout expires and future is not completed yet onTimeout callback is called and there i can tell to the view layer that network operation fails and show to the user some widget about it. But even with timeout expired the request to firebase database stays happening again and again, it's like the async event of request the database stays on dart event queue. I think this behavior is bad for performance aspects but if you're building your UI using a StreamBuilder with a little logic and code your requested data will be available right when you internet connection is back and with BLoC pattern the UI can respond easily to this event and we don't need provide a refresh button by example to user make the request again. I don't know if this is the right approach to implement this behavior but it works.
Approach 2 - Setting timeout inside from network request method
Below another firebase database request method
static Future<DataSnapshot> readUserNode( {#required String uid} ) async
=> USERS_REFERENCE.child(uid).once()
.timeout( Duration(seconds: Constants.NETWORK_TIMEOUT_SECONDS ) );
//note: Without timeout callback this line will throw a TimeoutException if the time expires
The usage in another BLoc component:
myBlocComponentMethod2(){
for( String uid in iterable ){
FirebaseUserHelper.readUserNode(uid: uid)
.then( (userSnapshot){
if (userSnapshot.value == null){
// do your stuffs
}
else {
// more stuffs to do
}
}).catchError( (error){
// when timeout expired we will catch the TimeoutException HERE and handling telling
// the UI what we need
} );
}
}
The big difference here that i get was in the behavior. In this second case since i put the timeout inside the request method when the timeout expires the request event do not run anymore, it's like that request event is removed from dart event queue. This can be good from performance perspective but now we need provide a refresh button in UI for user do the data again to get data from internet again.
I don't know if this workaround will solve your problem because you tell about SocketException what is not the case that i has described and i don't know what api you are using to make your requests. Anyway i hope that the concepts described in this post helps you implement a solution in your problem.
I wrote a simple application in Flutter using Dart. I use JWT tokens to authenticate user. Primary token is valid only 60 seconds.
When user send a request with expired token the webapi returns 401.
Then in my Dart code I check if statuscode of response is 401
If yes, then I send a request to RefreshToken endpoint and send request one more time (this request which returned 401 earlier).
If user does many actions too fast, expired token is renewed many times.
I'd like to avoid this.
In perfect soultion when token is being refreshing, other requests should wait.
I faced similar problem and tried to solve it using below approach.
I use flutter-redux to manage the state on client side.
Get jwt token after login
Decode the jwt token on client side as responded from server.
It contains a timeout - time of expiration.
Create a redux middleware on client side lets say _createRefreshTokenMiddleware.
Every request from client should go through this middle-ware before sending to server.
In this middle-ware, with every requests to server, check the token timeout, if token is expired, hold those request, send request to server to refresh token, wait until received new token, use this new token to send those request to server.
All other requests for which token will expire will wait on a common promise, lets say refreshTokenPromise to get refreshToken get resolved first. In this way you don't have to send multiple refreshToken requests.
If the token is still valid, let the requests to go through.
Refer below example -
Your middleware :
Middleware<AppState> _createRefreshTokenMiddleware() {
return (Store store, action, NextDispatcher next) async {
AppState appState = store.state;
AuthState auth = appState.auth;
if (isTokenExpired(auth)) {
if (auth.refreshTokenPromise == null) {
refreshToken(store).then((res) => next(action));
} else {
auth.refreshTokenPromise.then((res) => next(action));
}
}
next(action);
};
}
All the requests for which token is expired will wait on refreshTokenPromise to get resolved and as soon as that is resolved all of the pending requests will have new updated token set in request header (e.g).
Checking for token expiration :
bool isTokenExpired(AuthState auth) {
int bufferSeconds = 10;
if(auth != null && auth.authTokens != null && auth.authTokens.tokenExpiryTime != null) {
var currentTime = DateTime.now();
Duration durationRemaining = auth.authTokens.tokenExpiryTime.difference(currentTime);
return (durationRemaining.inSeconds - bufferSeconds) <= 0 ? true : false;
}
return false;
}
You send the request to refresh token 10 seconds before it is actually expired.
AuthState Model:
#immutable
class AuthState {
// properties
final bool isAuthenticated;
final bool isAuthenticating;
final AuthTokens authTokens;
final String error;
final Future<dynamic> refreshTokenPromise;
// constructor with default
AuthState({
this.isAuthenticated = false,
this.isAuthenticating = false,
this.authTokens,
this.error,
this.refreshTokenPromise,
});
}
Your auth-state model can be like above.
AuthToken:
#immutable
class AuthTokens {
// properties
final String accessToken;
final String refreshToken;
final DateTime tokenExpiryTime;
// constructor with default
AuthTokens({
this.accessToken,
this.refreshToken,
this.tokenExpiryTime,
});
}
Although I have given redux based solution here but same strategy can be applied anywhere else as well. I hope it helps.
As you correctly pointed out, the problem is that the authorization server receives too many token refresh request. Each particular user should only send one refresh request and rely on the results of that request.
Flutter's async package has a handy class called AsyncMemoizer for cases like this.
From the API reference:
A class for running an asynchronous function exactly once and caching its result.
An AsyncMemoizer is used when some function may be run multiple times
in order to get its result, but it only actually needs to be run once
for its effect. To memoize the result of an async function, you can
create a memoizer outside the function (for example as an instance
field if you want to memoize the result of a method), and then wrap
the function's body in a call to runOnce.
Assuming that the component of your app that handles all token requests is a singleton, you can cache the tokenrequests like that:
class TokenDataSource {
AsyncMemoizer<TokenResponse> tokenRequestMemoizer = AsyncMemoizer();
...
#override
Future<Tokens> verifyAndRefreshTokens() async {
var tokenResponse = await tokenRequestMemoizer.runOnce(() {
// run your token request code
});
// once the request is done, reset the memoizer so that future clients don't receive the cached tokens
tokenRequestMemoizer = AsyncMemoizer();
// return results
}
}
This will make all clients of TokenDataSource wait for the same token request instead of launching a new one.
We are trying to use RunImpersonated(handle, action); to be able to perform a REST call from a webserver but we have a hard time doing so. Project i ASP.NET Core 2.0 MVC.
We have the following general method made to establish a imp. context on behalf of the logged in wnd. user:
var user = WindowsIdentity.GetCurrent();
IntPtr token = user.Token;
SafeAccessTokenHandle handle = new SafeAccessTokenHandle(token);
WindowsIdentity.RunImpersonated(handle, action);
and basically in the action we make our REST call.
Thing is that we CAN run through without any problems running locally on our dev machines but we can't do the same on the remote webserver. Hence: impersonation.
Is our approach above for the imp. part right since we can't actually se if we promote any user-credentials?
We have tried different techniques in the REST-GET impl. as well without the above. On the other hand the above call are made closer to our controller and on REST impl. not having any specifics for imp. itself.
I was concerned with some time ago. As far as I can remmember, this worked for me:
Create an asynchronous action filter:
public class ImpersonationFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
var user = (WindowsIdentity)context.HttpContext.User.Identity;
await WindowsIdentity.RunImpersonated(user.AccessToken, async () =>
{
await next();
});
}
}
Register it as any other filter.