The right approach to keep users sessions - ios

I’m developing an app that uses external API and requires authentication through oauth2. In the response from api I get access token, refresh token and time in which token will expire. Everything is just fine until the time expire. So far before sending a request i check whether access token is valid:
if ([[AppCredentials sharedCredentials]tokenIsValid]) {
BackEnd *backendPUT = [[BackEnd alloc]init];
[backendPUT setDelegate:self];
[backendPUT updateMenuInDiary:menuDietyDoUpdate forDate:[[DziennikDietaModel sharedDziennikDietaModel]getwybranaDate] mealID:[menuDietyDoUpdate objectForKey:#"id"]];
monitor = [[UICustomLoadingMonitor alloc]initWithDefaultOptionsInView:[self view]];
[monitor start];
}else{
[[AppCredentials sharedCredentials] getAppTokenFromRefreshedToken];
}
when getAppTokenFromRefreshedToken is finished, method userTokenDownloaded is called using delegate.
The question is: how can I go back to that certain code which could not be executed in the first part of the if statement? Everything is asynchronous that is why i’ve got a problem.
In my view controller there are couple of methods that sends different requests to api and I need to differentiate which one needs to be called again.
What would be the right approach to this? closures ?

ok. solved it with blocks :) in the case someone was looking for an answer that is how i've done it:
BackEnd *backEnd = [[BackEnd alloc]init];
[backEnd setDelegate:self];
if ([[AppCredentials sharedCredentials]tokenIsValid]) {
NSLog(#"TOKEN VALID NO BLOCKS");
[backEnd updateMenuInDiary:menuDietyDoUpdate];
}else{
NSLog(#"USING BLOCKS");
[backEnd getAppTokenFromRefreshToken:refreshedToken withCompletionBlock:^{
NSLog(#"ponowna proba z bloku");
[backEnd updateMenuInDiary:menuDietyDoUpdate];
}];
}
works great :)

Related

Do I need to refresh the access token when using Microsoft Graph and How to do it?

I'm using Microsoft Graph SDK for my iOS Application.
Do I need to manually refresh the access token when it expired?
The access token I'm talking about is:
NXOAuth2AccountStore.sharedStore().accounts[0].accessToken
I have tested that I can still query even the accessToken expired. At the time I first logged in, the expired time is 3600 secs. So, I waited 2 hours, test to get user info, events again and still can get it.
I have dump "accessToken.hasExpired" and "accessToken.expiresAt" to make sure access token is expired
Thanks
* More Details *
I follow the sample here:
https://github.com/microsoftgraph/ios-swift-connect-sample
I cannot find any documents about refresh access token on Microsoft Graph:
https://graph.microsoft.io/en-us/code-samples-and-sdks
Yes, you need to refresh tokens periodically when using Graph in your application. More detailed documentation is available through Azure AD's site: https://learn.microsoft.com/en-us/azure/active-directory/active-directory-authentication-scenarios
The suggested auth library you are using contains a method for refreshing this token:
#implementation NXOAuth2AuthenticatorRefreshCallback
If I haven't answered your question, could you be more specific about what you are trying to accomplish? Are you able to use an expired token or are you unable to refresh your old one?
Use this code whenever you need to refresh the access token.
This will act as a patch to predefined code provided in graph sdk and you can extract the token from the method :
+(id)tokenWithResponseBody:(NSString *)theResponseBody tokenType:(NSString *)tokenType;
[MSGraphClient setAuthenticationProvider:AppDel.authentication.authProvider];
_graphClient = [MSGraphClient client];
NSMutableURLRequest * sampleReq = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://graph.microsoft.com/v1.0/me"]];
[_graphClient.authenticationProvider appendAuthenticationHeaders:sampleReq completion:^(NSMutableURLRequest *request, NSError *error){
if(error == nil)
{
}
else
{
[self showToast:#"" message:#"Failure in refresh 0365 token"];
}
}];

Best way to check if sharing via SLComposeViewController is successful with a limited (or no) Internet connection

Apple's documentation is clear on using SLComposeViewController to provide sharing capabilites with other social networks such as Twitter and Facebook.
Typical code will use isAvailableForServiceType to verify if a particular service is available and then add a completion handler to the view controller where an SLComposeViewControllerResult can be checked for either SLComposeViewControllerResultCancelled or SLComposeViewControllerResultDone, which check if the user tapped on the 'Post' button or on the 'Cancel' button after the sharing view has been displayed.
The issue here is that if you use SLComposeViewControllerResultDone to validate that the user made the request, you don't actually check if it was successful such as when the user has limited or no connectivity.
I have tried with one of my apps to test this and have noticed that the SLComposeViewControllerResultDone constant is still valid even if airplane mode is turned on such that a request is not possible. What this means is that the user fills out the sharing view fields and taps on 'Post' and my success code executes even though I should really be checking to make sure that the post was indeed successful.
Currently, I figure that the best option is to check for an Internet connection using the standard Reachability options (as recommended here) and disable the sharing button if a connection is not available, but I'm not sure if this is the best solution as it doesn't account for a limited connection where the user can tap on 'Post' but the actual request is unsuccessful.
My question is what is the best method of detecting if a sharing request has successfully completed?
then you need to make sure that you do not write below line in didSelectPost
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];
and once you get success or fail based on that in the your request handler you can write above line, so your didSelectPost should be like :
- (void)didSelectPost {
NSExtensionItem *inputItem = self.extensionContext.inputItems.firstObject;
NSItemProvider *attachment = inputItem.attachments.firstObject;
if ([attachment hasItemConformingToTypeIdentifier:#"public.url"])
{
//NSString *strLink = [attachement loadItemForTypeIdentifier:#"public.url" options:nil completionHandler:nil];
[attachment loadItemForTypeIdentifier: #"public.url"
options: nil
// make your request here
}];
}
}

Best solution to refresh token automatically with AFNetworking?

Once your user is logged in, you get a token (digest or oauth) which you set into your HTTP Authorization header and which gives you the authorization to access your web service.
If you store your user's name, password and this token somewhere on the phone (in user defaults, or preferably in the keychain), then your the user is automatically logged in each time the application restarts.
But what if your token expires? Then you "simply" need to ask for a new token and if the user did not change his password, then he should be logged in once again automatically.
One way to implement this token refreshing operation is to subclass AFHTTPRequestOperation and take care of 401 Unauthorized HTTP Status code in order to ask a new token. When the new token is issued, you can call once again the failed operation which should now succeeds.
Then you must register this class so that each AFNetworking request (getPath, postPath, ...) now uses this class.
[httpClient registerHTTPOperationClass:[RetryRequestOperation class]]
Here is an exemple of such a class:
static NSInteger const kHTTPStatusCodeUnauthorized = 401;
#interface RetryRequestOperation ()
#property (nonatomic, assign) BOOL isRetrying;
#end
#implementation RetryRequestOperation
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *, id))success
failure:(void (^)(AFHTTPRequestOperation *, NSError *))failure
{
__unsafe_unretained RetryRequestOperation *weakSelf = self;
[super setCompletionBlockWithSuccess:success failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// In case of a 401 error, an authentification with email/password is tried just once to renew the token.
// If it succeeds, then the opration is sent again.
// If it fails, then the failure operation block is called.
if(([operation.response statusCode] == kHTTPStatusCodeUnauthorized)
&& ![weakSelf isAuthenticateURL:operation.request.URL]
&& !weakSelf.isRetrying)
{
NSString *email;
NSString *password;
email = [SessionManager currentUserEmail];
password = [SessionManager currentUserPassword];
// Trying to authenticate again before relaunching unauthorized request.
[ServiceManager authenticateWithEmail:email password:password completion:^(NSError *logError) {
if (logError == nil) {
RetryRequestOperation *retryOperation;
// We are now authenticated again, the same request can be launched again.
retryOperation = [operation copy];
// Tell this is a retry. This ensures not to retry indefinitely if there is still an unauthorized error.
retryOperation.isRetrying = YES;
[retryOperation setCompletionBlockWithSuccess:success failure:failure];
// Enqueue the operation.
[ServiceManager enqueueObjectRequestOperation:retryOperation];
}
else
{
failure(operation, logError);
if([self httpCodeFromError:logError] == kHTTPStatusCodeUnauthorized)
{
// The authentication returns also an unauthorized error, user really seems not to be authorized anymore.
// Maybe his password has changed?
// Then user is definitely logged out to be redirected to the login view.
[SessionManager logout];
}
}
}];
}
else
{
failure(operation, error);
}
}];
}
- (BOOL)isAuthenticateURL:(NSURL *)url
{
// The path depends on your implementation, can be "auth", "oauth/token", ...
return [url.path hasSuffix:kAuthenticatePath];
}
- (NSInteger)httpCodeFromError:(NSError *)error
{
// How you get the HTTP status code depends on your implementation.
return error.userInfo[kHTTPStatusCodeKey];
}
Please, be aware that this code does not work as is, as it relies on external code that depends on your web API, the kind of authorization (digest, oath, ...) and also which kind of framework you use over AFNetworking (RestKit for example).
This is quite efficient and has proved to work well with both digest and oauth authorization using RestKit tied to CoreData (in that case RetryRequestOperation is a subclass of RKManagedObjectRequestOperation).
My question now is: is this the best way to refresh a token?
I am actually wondering if NSURLAuthenticationChallenge could be used to solve this situation in a more elegant manner.
Your current solution works and you have the code for it, there might be a reasonable amount of code to achieve it but the approach has merits.
Using an NSURLAuthenticationChallenge based approach means subclassing at a different level and augmenting each created operation with setWillSendRequestForAuthenticationChallengeBlock:. In general this would be a better approach as a single operation would be used to perform the whole operation rather than having to copy it and update details, and the operation auth support would be doing the auth task instead of the operation completion handler. This should be less code to maintain, but that code will likely be understood by less people (or take longer to understand by most) so the maintenance side of things probably balances out over all.
If you're looking for elegance, then consider changing, but given that you already have a working solution there is little gain otherwise.
I was searching an answer for this problem and "Matt", the creator of AFNetworking, suggest this:
the best solution I've found for dealing with this is to use dependent
NSOperations to check for a valid, un-expired token before any
outgoing request is allowed to go through. At that point, it's up to
the developer to determine the best course of action for refreshing
the token, or acquiring a new one in the first place.

Authenticating with ReactiveCocoa

I'm building an app on top of ReactiveCocoa and Octokit.objC (github library). As part of my effort I'm using Octokits ReactiveCocoa signals to access resources that require authentication. A previous question 'Retrying an asynchronous operation using ReactiveCocoa' does a nice job covering the case where the user wants to 'retry an asynchronous operation' once. I'm trying to figure out how to handle the case where you might want to retry several times.
In my specific case if authentication fails I want to go ask the user for their credentials. I'll either ask the user for their credentials a few times (2 or 3) and then halt if they fail or I'll just keep asking them for their credentials until they succeed.
Any help would be appreciated. Thanks - AYAL
There is an operator called -retry: which accepts a count parameter. If you apply this operator to a signal, and that signal returns an error, it will re-subscribe to the signal (up to the specified number of times) when the error is received. So what you need is a signal that, when subscribed to, prompts the user for credentials.
#weakify(self);
RACSignal *requestCredentials = [RACSignal defer:^{
#strongify(self);
// (Prompt the user for credentials.)
if (successful)
{
self.cachedCredentials = credentials;
return [self authenticate:credentials];
}
else
{
return [RACSignal error:[[MyError alloc] init]];
}
}];
// We try to authenticate using the cached credentials (the
// `-authenticate:` method returns a signal that attempts
// authentication when it is subscribed to). If the initial
// attempt to authenticate fails, we try 3 times to get the
// user to enter the correct credentials.
return [[self authenticate:self.cachedCredentials]
catchTo:[requestCredentials retry:3]];

iOS OneDrive (skydrive) app displays permissions dialog every time it runs

I'm developing an iOS app that gives users access to their OneDrive/SkyDrive and I've run into a very annoying issue:
The very first time a user links the app to their OneDrive, everything goes as expected:
They have to enter a user id and password
Then they have to agree to let the app access their info
Then they get to browse their OneDrive
That's all good.
But, if the app closes, and you try to access the OneDrive again, rather than skipping straight to #3, and being able to access the OneDrive, they are stopped at step #2 (step 1 is skipped, as expected) and they have to agree again to let the app access their info.
The code is taken directly from the iOS examples in the online documentation (with some slight modification based on samples found here on Stack Overflow), but, here it is for inspection:
- (void) onedriveInitWithDelegate:(id)theDelegate {
self.onedriveClient = [[LiveConnectClient alloc] initWithClientId:MY_CLIENT_ID
delegate:theDelegate
userState:#"initialize"];
}
And then, theDelegate implements this:
- (void)authCompleted:(LiveConnectSessionStatus) status
session:(LiveConnectSession *) session
userState:(id) userState {
NSLog(#"Status: %u", status);
if ([userState isEqual:#"initialize"]) {
NSLog( #"authCompleted - Initialized.");
if (session == nil) {
[self.onedriveClient login:self
scopes:[NSArray arrayWithObjects:#"wl.basic", #"wl.signin", #"wl.skydrive_update", nil]
delegate:self
userState:#"signin"];
}
}
if ([userState isEqual:#"signin"]) {
if (session != nil) {
NSLog( #"authCompleted - Signed in.");
}
}
}
I thought that perhaps the status value might give a clue and that maybe I could avoid the login call, but it's always zero/undefined when I get to authCompleted after calling initWithClientId. (And session is always nil.)
Is there a scope I'm missing? Is there a different call to make rather than a straight-up login call? Or is it more complicated than that? I've seen reference to "refresh tokens" related to OAuth2 login, but I've not been able to find any concrete examples of how they might be used in this situation.
Any help and/or insights greatly appreciated.
Diz
Well, it turns out that the answer is pretty simple here. I just needed to add the "wl.offline_access" scope to my list of scopes during the initial login operation. The docs didn't really imply this type of behavior for this scope, but, that did the trick for me.
With this new scope added, subsequent invocations of the app no longer bring up the "agree to give the app these permissions" dialog, and I can go straight to browsing the OneDrive.
(Credit where it's due: Stephane Cavin over at the microsoft forums gave me the tip I needed to work this out. Gory details are here:
http://social.msdn.microsoft.com/Forums/en-US/8c5c7a99-7e49-401d-8616-d568eea3cef1/ios-onedrive-skydrive-app-displays-permissions-dialog-every-time-it-runs?forum=onedriveapi )
Diz

Resources