Is it a good practice to reload credentials before every request? - swift

I'm working on an iOS app that works with a backend made in laravel, this api manage login and information exchange via JSON web tokens.
To prevent the user to access a web route with an expired token I check the user's credentials before every web call.
My question is, is it a good practice? Because when I started thinking about it the user is accessing twice the amount of times to my server.
For example this is the function to access they're information.
/// Get user's info form the database
///
/// - Parameter completed: Completition Block
func getInfo(completed: #escaping DownloadComplete){
let token = self.getToken()
print(self.getError())
if(token != nil && token != ""){
//Here is where I check credentials.
self.checkCredentials(completed: {
let url = "\(Base_URL)\(myInfo)\(self.getToken() ?? "")"
Alamofire.request(url, method: HTTPMethod.get).responseJSON { respone in
if let result = respone.result.value as? Dictionary<String, AnyObject>{
if let user = result["User"] as? Dictionary<String, AnyObject>{
var Info = Alumno()
if let id = user["id"] as? Int64!{
Info.id = id
}
...
self.userInfo = Info
completed()
}
}
}
})
}
}
The check credentials functions asks the server if the user's token is still valid and if it is it returns
{
"Status": "Success"
}

My answer assumes your server validates the token, and you are not relying on the app to validate the token.
With that assumption, I think it depends on what you can do if the token is expired.
If the only thing you can do is prompt the user to sign in again, then I would probably just make the web request and handle the 401 Not Authorized.
However, if you have the ability to silently refresh the user's token, you may want to preemptively refresh the token if is close to expiring.
In my application, before every web request the app checks if the user's access token will expire within the next 3 minutes, and if so, attempts to refresh it before making the request. If the refresh is successful, the web request is made with the new access token. If the refresh fails, the web request is not made and the app prompts the user to sign in instead. If for any reason the web request still fails after that with 401, the app prompts the user to sign in.

I would do it like once every time the user opens the app not every call.
Other options:
You could also give them a code that they enter that's good for so long.
You could have them sign in when they first download the app. Then they would always be logged in. Then when the token is expired a little popup would say "Something Something Something"

Related

Duplicated anonymous users when session expired in Parse platforms

if let cachedUser = PFUser.current() {
// proceed to save some objects
} else {
PFAnonymousUtils.logIn{ (user, error) in
// proceed to save some objects
if ((error as NSError).code == 209) {
// session expired, logout and call PFAnonymousUtils.logIn again later
PFUser.logOut()
}
}
}
For a simple Swift mobile app, we save data on parse backend anonymously. If there is session expiration error (1 year default on Parser server), we will have to do something about it or we wont be able to save anything anymore. We therefore logout and re-login again.
Once we logout and re-login again, this creates a second new User on the backend.
This creates a problem - we no longer have an accurate picture of the number of users on the backend.
What was wrong in the flow above? Is there a way to prevent duplicated anonymous user when handling expired session?
It is possible to increase the default session duration in your server configuration.
You can also add the code below to your server configuration...
expireInactiveSessions: false
This thread may provide further useful insights into this issue.

iOS Today Widget utilize Uber login token

I have an app with an Uber login that gives access to restricted API calls (info on the current ride). I'd like to share the login token with the associated Today Widget so it can make similar calls.
I'm already sharing data with a UserDefaults suite, and I'm using the UberRides SDK. In digging into the RidesClient object it seems to try to use the keychain for storing/sharing the login token, and I set up a shared keychain to try to take advantage of this, but no luck. Restricted API calls from the widget return as unauthorized. Any suggestions?
Here's some code from the widget (note the user already authenticated in the main app):
let rc = RidesClient()
rc.fetchCurrentRide { ride, response in
if ride == nil { print("NO CURRENT RIDE") }
print(response.response)
print(response.error?.title)
if let ride = ride {
// do something
} else {
self.ride = nil
}
}
This returns an unauthorized response. I traced into the RidesClient (which is an object in the UberRides SDK), and see the code where the token is "supposed" to come from the keychain, but it doesn't.
I also tried generating my own URL request in the widget, using the login token passed through shared UserDefaults. This followed the standard HTTP access approach, putting the token in the Authorization header. But I got the same unauthorized response.
Here's some more details on the SDK approach:
Main app uses the LoginButton in native mode:
let scopes: [RidesScope] = [.Profile, .Places, .Request, .AllTrips]
let loginManager = LoginManager(accessTokenIdentifier: Configuration.getDefaultAccessTokenIdentifier(), keychainAccessGroup: "com.MYCOMPANY.MYAPP.share", loginType: .native)
let loginButton = LoginButton(frame: loginFrame, scopes: scopes, loginManager: loginManager)
loginButton.presentingViewController = self
loginButton.delegate = self
view.addSubview(loginButton)
The login button does the right thing and authorizes in the Uber app. I can see the token returned in the delegate callback didCompleteLoginWithToken. However, I can then check for the token:
let token = TokenManager.fetchToken(Configuration.getDefaultAccessTokenIdentifier(), accessGroup: "com.MYCOMPANY.MYAPP.share")
print(token)
The token is "nil". I don't think the SDK is saving the token into the access group keychain.
When I use the default keychain (not the keychainAccessGroup), the login in the app works fine and I can get the login token back and make restricted calls to the API. However, that doesn't help the widget, which needs the token from the access group keychain.
Solved!! After many hours of debugging, and searching. What was not clear in ANY documentation is the keychainAccessGroup MUST include the AppIndentifierPrefix. That's the 10 character identifier associated with the App ID. So, instead of using "com.MYCOMPANY.MYAPP.share", it's "APPID.com.MYCOMPANY.MYAPP.share" for the keychainAccessGroup.

Swift : How to check if Facebook access token has expired?

I'm currently using
if FBSDKAccessToken.currentAccessToken() != nil {
//Do Something
}
else {
//Passing the access token to my server
}
to check if a user is already logged in. But the problem in this approach is if a user opens my app after long time the currentAccessToken might have expired but since it is not nil it will send the expired token to the server.
How can I put a check if currentAccessToken is valid or not? I'm using version 2.4 of FBSDK
I think you may request https://graph.facebook.com/me?access_token=TOKEN
If token is not valid you will get oauth error in json response, otherwise you will get some info about user.
But better, for my mind, do the same on server side.

servicestack and facebook canvas app authentication

the facebook canvas app gets a "signed_request" parameter when user visits the canvas url via facebook.
How do i use this to authenticate the user on servicestack, so that i get the user session in servicestack.
the user will already be signed up for the app and will have records in the servicestack user repositories.
Should i set the canvas url to /auth/facebook ? with additional ?Continue=/target_url
Will this authenticate the user and send him to the target_url?
Or should i handle the canvas request and then use AuthService to authenticate the user using the "signed_request" param? if this is the case then, how do i proceed with it ?
Here's how I managed the case:
I handled the FB canvas request, receiving the "signed_request" parameter. Then by decoding the BASE64 encoded string (and verifying with HMAC SHA256), I got the FB userId.
if (isMatch)
{
string message = UTF8Encoding.UTF8.GetString(msg);
var output = message.FromJson<Dictionary<string, string>>();
string user = output["user_id"];
OAuthTokens tokens = new OAuthTokens();
tokens.Provider = "facebook";
tokens.UserId = user;
UserSession.IsAuthenticated = true;
((FacebookAuthProvider)AuthService.GetAuthProvider("facebook")).OnAuthenticated(this, UserSession, tokens, new Dictionary<string, string>());
return UserSession.ToJson();
}
I'm not sure whether this is the best way to manually get the user authenticated. But so far, this technique has worked.

how to refresh an oauth token when using the Facebook iPhone SDK

I'm using the Facebook SDK for iOS in my app: http://github.com/facebook/facebook-ios-sdk
The oAuth token expires after about 2 hours. How can I "refresh" the oAuth token without having to call the [Facebook authorize...] method again - which shows an empty Facebook dialog briefly if the user had previously logged in? What I want to avoid is requiring the user to re-login to FB each time they use the app - say, day to day.
I am already saving / restoring oAuth tokens when the app exits / starts. And I can check to see if the token is valid using [Facebook isSessionValid], or by checking the expire time on the token. But what to do if the token has expired? I've read that it is possible to "refresh" the token, but I don't understand how this is done.
I don't want to request "offline_access" permission, which would give me a "forever" token.
Help!?
Facebook's implementation of OAuth doesn't support token refresh.
You have 2 types of access_tokens in Facebook. Short term token, which is given by default and a long term token which is given if you request offline_access. If refresh token were supported, it was the same as giving a offline_access token for all apps.
As long as the user has an active facebook session on your web control, you can request a new access_token just by accessing https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_uri=http://www.facebook.com/connect/login_success.html&response_type=token or probably some iOS SDK command that does the same (never worked with it so I can't tell). This kind of request will not ask the user to login again, but will use the previous session that was created during the first login.
Since none of these answers actually addressed the question I am going to detail how I have implemented OAuth token refresh using The Facebook SDK.
The SDK will automatically refresh your tokens when you make requests however, in my scenario we send the tokens to our servers and we need to use the latest token. So when our server indicates that we need new tokens this is what we do:
Note You can either pass the AppID into the FBSession or you can add the FacebookAppID key to your App's plist (this is what we do).
- (void)renewFacebookCredentials {
if (FBSession.activeSession.state == FBSessionStateOpen ||
FBSession.activeSession.state == FBSessionStateOpenTokenExtended) {
[self sessionStateChanged:[FBSession activeSession] state:[FBSession activeSession].state error:nil];
} else {
// Open a session showing the user the login UI
// You must ALWAYS ask for public_profile permissions when opening a session
[FBSession openActiveSessionWithReadPermissions:#[#"public_profile",#"email"]
allowLoginUI:NO
completionHandler:^(FBSession *session, FBSessionState state, NSError *error) {
//this block will run throughout the lifetime of the app.
[self sessionStateChanged:session state:state error:error];
}];
}
}
The you can use the sessionStateChanged: method that Facebook include in their documentation but a simplified handler looks like this:
- (void)sessionStateChanged:(FBSession *)session state:(FBSessionState) state error:(NSError *)error {
// If the session was opened successfully
NSString *accessToken;
if (!error && state == FBSessionStateOpen && [[session accessTokenData] accessToken]){
// Show the user the logged-in UI
//#see http://stackoverflow.com/questions/20623728/getting-username-and-profile-picture-from-facebook-ios-7
accessToken = [[session accessTokenData] accessToken];
//Now we have an access token, can send this to the server...
} else {
//No access token, show a dialog or something
}
//either call a delegate or a completion handler here with the accessToken
}
Be aware that some of the FBSession API calls check for thread affinity so I found that I had to wrap all my FBSession calls inside a dispatch_async(dispatch_get_main_queue(), ^{...
Just do [ [FBSession session] resume] if it return false do login again
Facebook iOS SDK doesn’t handle the session storage.
FBSessionDelegate is a callback interface that your application should implement. it's methods will be invoked on application's successful login or logout.
See the example application facebook-ios-sdk/sample/DemoApp/Classes/DemoAppViewController.m for fbDidLogin, fbDidNotLogin and fbDidLogout methods
As per the Facebook SDK documentation:
The Facebook SDK automatically refreshes the user's session if
necessary. When it does this, the state also transitions to the
FBSessionStateOpenTokenExtended state.
Also to further clarify (since SDK version 3.1):
The SDK refreshes session automatically on API calls.
It also refreshes the token data as needed when follow-on authentication or Facebook API calls made using the SDK.
As of today Facebook is supposed to refresh tokens automatically, requests to GraphAPI can be done without providing token string either (Facebook handles it under the hood).
Moreover, if it happens that user wasn't using app for a long time and his token managed to expire, on your next request to Graph API an alert will be shown by Facebook's SDK asking user to relogin (all that is handled by Facebook and when done - will return into FBSDKGraphRequest's closure).
However, if someone really has a reason to manually refresh access token, here's an example (Swift 4):
private var selfDestructableNotificationToken: NotificationTokenThatAutomaticallyRemovesObserver?
final class NotificationTokenThatAutomaticallyRemovesObserver: NSObject { // more info here: https://oleb.net/blog/2018/01/notificationcenter-removeobserver/
let token: Any
init(_ token: Any) { self.token = token }
deinit { NotificationCenter.default.removeObserver(token) }
}
...
if let currentFBToken = FBSDKAccessToken.current() { // if this is a Facebook user, not an email-based user
if FBSDKAccessToken.currentAccessTokenIsActive() { // and his token has not expired yet
let token = NotificationCenter.default.addObserver(forName: NSNotification.Name.FBSDKAccessTokenDidChange, object: nil, queue: OperationQueue.main) { notification in
if let userInfo = notification.userInfo, let refreshedToken = userInfo["FBSDKAccessToken"] as? FBSDKAccessToken {
self.fbAccessToken = refreshedToken.tokenString
} else {
self.fbAccessToken = currentFBToken.tokenString // falling back to using an old token (better than none)
}
}
self.selfDestructableNotificationToken = NotificationTokenThatAutomaticallyRemovesObserver(token)
FBSDKAccessToken.refreshCurrentAccessToken { _, _, error in
if let error = error {
print("failed to refresh Facebook token with error \(error.localizedDescription)")
self.fbAccessToken = currentFBToken.tokenString // falling back to an old token (better than none)
}
}
} else if FBSDKAccessToken.current().isExpired { // unlucky user. Probably returned to the app after > 2 months break
self.fbAccessToken = currentFBToken.tokenString // assigning expired token. Facebook will ask user to relogin as soon as we call Graph API with that expired token
}
}