ArtStyle
ArtStyle

Reputation: 353

Right way to refresh Gmail API access token in objective-c

I get access token in that method:

- (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
  finishedWithAuth:(GTMOAuth2Authentication *)authResult
             error:(NSError *)error {
if (error != nil) {
    [self showAlert:@"Authentication Error" message:error.localizedDescription];
    self.service.authorizer = nil;
}
else {
    self.service.authorizer = authResult;

    NSLog(@"Token: %@ id: %@", authResult.accessToken, authResult.userID);
    [self makeGmailLabelVisibleWithToken:authResult.accessToken]; //make an authorized request to gmailAPI with the access token

    [self dismissViewControllerAnimated:YES completion:nil];

  }
}

So, after auth that's works fine, but after a while it stops working (I guess because token has expired). Also, if I use

[authResult refreshToken]

instead of

authResult.accessToken

it won't work.

So what's the correct way to refresh the Gmail access token, in which method should I do this?

P.S: documentation says that the

- (void) refreshTokensWithHandler:(GIDAuthenticationHandler)handler

should help, but I have not found any samples with it.

Upvotes: 1

Views: 937

Answers (2)

ArtStyle
ArtStyle

Reputation: 353

So, this is actually simple. To refresh the token you need to use following code:

self.service = [[GTLServiceGmail alloc] init];
self.service.authorizer =
[GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName
                                                      clientID:kClientID
                                                  clientSecret:nil];

[[GIDSignIn sharedInstance] setScopes:[NSArray arrayWithObject: @"https://www.googleapis.com/auth/plus.me"]];
[GIDSignIn sharedInstance].clientID = kClientID;

GTMOAuth2Authentication *auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName
                                                                                      clientID:kClientID
                                                                                  clientSecret:nil];

NSLog(@"accessToken: %@", auth.accessToken); //If the token is expired, this will be nil

// authorizeRequest will refresh the token, even though the NSURLRequest passed is nil
[auth authorizeRequest:nil
     completionHandler:^(NSError *error) {
         if (error) {
             NSLog(@"error: %@", error);
         }
         else {
             [USER_CACHE setValue:auth.accessToken forKey:@"googleAccessToken"];
         }
     }];

You can paste it in ViewDidLoad method for instance. So after execution of this code you will have valid access token in UserDefaults (which is USER_CAHCE in my example).

Upvotes: 0

ReyAnthonyRenacia
ReyAnthonyRenacia

Reputation: 17613

To obtain refresh token, you have to enable server-side APIA access for your app. "To obtain an access token and refresh token for your server, you can request a one-time authorization code that your server exchanges for these two tokens. You request the one-time code by specifying your server's client ID along with your other GIDSignIn parameters. After you successfully connect the user, you will find the one-time code as the auth parameter server_code accessible via the finishedWithAuth:error handler."

  1. Configure an iOS app project as described in Start Integrating.
  2. Define your app delegate's application:didFinishLaunchingWithOptions: method as described above in Enable Sign-In, but for this implementation you will set the serverClientID property as shown below.
-(BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  [GIDSignIn sharedInstance].clientID = @"APP_CLIENT_ID";
  [GIDSignIn sharedInstance].serverClientID = @"SERVER_CLIENT_ID";

  // Additional scopes, if any
  // [GIDSignIn sharedInstance].scopes = @[ @"other_scope" ];

  return YES;
}
  1. After the user is signed in, retrieve the one-time authorization code:
-(void)signIn:(GIDSignIn *)signIn didSignInForUser:(GIDGoogleUser *)user
    withError:(NSError *)error {
  // Perform any operations on signed in user here.
  // user.serverAuthCode now has a server authorization code!
}
  1. Securely pass the serverAuthCode string to your server using HTTPS POST.
  2. On your app's backend server, exchange the auth code for access and refresh tokens. Use the access token to call Google APIs on behalf of the user and, optionally, store the refresh token to acquire a new access token when the access token expires.

You can make use of HTTP/REST calls.

Here's an HTTP call in Python, just use the Objective-C equivalent.

import json
import flask
import requests


app = flask.Flask(__name__)

CLIENT_ID = '123456789.apps.googleusercontent.com'
CLIENT_SECRET = 'abc123'  # Read from a file or environmental variable in a real app
SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly'
REDIRECT_URI = 'http://example.com/oauth2callback'


@app.route('/')
def index():
  if 'credentials' not in flask.session:
    return flask.redirect(flask.url_for('oauth2callback'))
  credentials = json.loads(flask.session['credentials'])
  if credentials['expires_in'] <= 0:
    return flask.redirect(flask.url_for('oauth2callback'))
  else:
    headers = {'Authorization': 'Bearer {}'.format(credentials['access_token'])}
    req_uri = 'https://www.googleapis.com/drive/v2/files'
    r = requests.get(req_uri, headers=headers)
    return r.text


@app.route('/oauth2callback')
def oauth2callback():
  if 'code' not in flask.request.args:
    auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code'
                '&client_id={}&redirect_uri={}&scope={}').format(CLIENT_ID, REDIRECT_URI, SCOPE)
    return flask.redirect(auth_uri)
  else:
    auth_code = flask.request.args.get('code')
    data = {'code': auth_code,
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_SECRET,
            'redirect_uri': REDIRECT_URI,
            'grant_type': 'authorization_code'}
    r = requests.post('https://www.googleapis.com/oauth2/v4/token', data=data)
    flask.session['credentials'] = r.text
    return flask.redirect(flask.url_for('index'))


if __name__ == '__main__':
  import uuid
  app.secret_key = str(uuid.uuid4())
  app.debug = False
  app.run()

Upvotes: 1

Related Questions