Short version:
I want to use the Google OAuth2 client for Objective C to create a valid GTMOAuth2Authentication
object for me to use in my app, with an offline access token I get from my backend. How can I accomplish that?
My situation:
My app uses Analytics read-only scope to get some data about the user's website(s). The way I accomplish this, is by logging in with the GTMOAuth2ViewControllerTouch
ViewController, provided by the Google OAuth2 client for Objective C. It then gives me the valid GTMOAuth2Authentication
object, that I can use to query Google Analytics via the Google API client.
Now, we don't want to give the users Google Analytics access (some don't have Google accounts and in general, we want to keep the information simple via the app). This means we got to login with our account (that has access to all the websites' Analytics data). Obviously we can't give our users our credentials, so we had to find a solution for that.
My plan:
I think this problem could be solved by requesting our (offline access) token string from our backend (via SSL encryption), saving it to the user's keychain and use it further in the application to query Analytics. We then let the user login at our service (so we can determine which websites the user has access to), and show the data.
My problem:
I have searched everywhere, looked through the (very thin) documentation by Google, inspected the GTMOAuth2Authentication
source code, but I cannot seem to wrap my head around the problem. It seems to me there would be a solution like this (because I use a similar approach in our CMS to let a user post to our Facebook wall), so what am I missing here?
The current login code:
GTMOAuth2ViewControllerTouch *viewController = [[GTMOAuth2ViewControllerTouch alloc]
initWithScope:scope
clientID:kMyClientID
clientSecret:kMyClientSecret
keychainItemName:kKeychainItemName
completionHandler:
^(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error) {
if (error != nil) {
// Authentication failed
DebugLog(@"Failed!");
} else {
// Authentication succeeded
DebugLog(@"Success!");
// Update interface
self.loginButton.hidden = YES;
self.authentication = auth;
}
}];
What I have tried:
I have tried manually creating a GTMOAuth2Authentication
object and setting all the parameters myself (scope, clientid, secretid, access token, refresh token, callbackuri, token url, etc), but I get returned a 401: Login Required error when I try to use the object for queries. So I guess that is not the way to do it.
Links:
Thanks for reading!
You don't need a GTMOAuth2Authentication instance. What you need is to create you own class that implement the GTMFetcherAuthorizationProtocol protocol, and assign it as your authorizer.
MyAuth *myAuth = [[MyAuth alloc] initWithAccessToken:accessToken];
googleService.authorizer = myAuth;
This class needs to authorize the request based on the access token you already have. The code of this class should be similar to this.
@interface MyAuth : NSObject<GTMFetcherAuthorizationProtocol>
@property (copy, nonatomic) NSString *accessToken;
@property (copy, readonly) NSString *userEmail;
@end
@implementation MyAuth
- (void)authorizeRequest:(NSMutableURLRequest *)request
delegate:(id)delegate
didFinishSelector:(SEL)sel {
if (request) {
NSString *value = [NSString stringWithFormat:@"%s %@", GTM_OAUTH2_BEARER, self.accessToken];
[request setValue:value forHTTPHeaderField:@"Authorization"];
}
NSMethodSignature *sig = [delegate methodSignatureForSelector:sel];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setSelector:sel];
[invocation setTarget:delegate];
[invocation setArgument:(__bridge void *)(self) atIndex:2];
[invocation setArgument:&request atIndex:3];
[invocation invoke];
}
- (void)authorizeRequest:(NSMutableURLRequest *)request
completionHandler:(void (^)(NSError *error))handler {
if (request) {
NSString *value = [NSString stringWithFormat:@"%@ %@", @"Bearer", self.accessToken];
[request setValue:value forHTTPHeaderField:@"Authorization"];
}
}
- (BOOL)isAuthorizedRequest:(NSURLRequest *)request {
return NO;
}
- (void)stopAuthorization {
}
- (void)stopAuthorizationForRequest:(NSURLRequest *)request {
}
- (BOOL)isAuthorizingRequest:(NSURLRequest *)request {
return YES;
}
Hope it helps.
You need is to create you own class that implement the GTMFetcherAuthorizationProtocol
protocol, and assign it as your authorizer.
Here is Xcode 10.1 (2019) updated fixed non-crashing version:
NSString *accessToken = "ya29.c.Elu-Bmq2eNaGPnIe3-Q4GuTAhuQZmyC6ylm6zCROBtyKEJQFJdlIBTUVlUjOOoaT0cQae_OGdxNM4ayRT_0yg121kD8ouX4SGbllPWRkiGHRbsZRuMPX2QCgMoIO";
MyAuth *auth = [[MyAuth alloc] initWithAccessToken:accessToken];
googleService.authorizer = auth;
MyGTMFetcherAuthorization.m
#import <Foundation/Foundation.h>
#import <GTMSessionFetcher/GTMSessionFetcher.h>
@interface MyAuth : NSObject<GTMFetcherAuthorizationProtocol>
+ (MyAuth *)initWithAccessToken:(NSString *)accessToken;
@end
// Until all OAuth 2 providers are up to the same spec, we'll provide a crude
// way here to override the "Bearer" string in the Authorization header
#ifndef GTM_OAUTH2_BEARER
#define GTM_OAUTH2_BEARER "Bearer"
#endif
@interface MyAuth ()
@property (strong, nonatomic) NSString *accessToken;
@end
@implementation MyAuth
+ (MyAuth *)initWithAccessToken:(NSString *)accessToken {
MyAuth *auth = [[MyAuth alloc] init];
auth.accessToken = [accessToken copy];
return auth;
}
- (void)authorizeRequest:(NSMutableURLRequest *)request
delegate:(id)delegate
didFinishSelector:(SEL)sel {
[self setTokeToRequest:request];
NSMethodSignature *sig = [delegate methodSignatureForSelector:sel];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setSelector:sel];
[invocation setTarget:delegate];
[invocation setArgument:(&self) atIndex:2];
[invocation setArgument:&request atIndex:3];
[invocation invoke];
}
- (void)authorizeRequest:(NSMutableURLRequest *)request
completionHandler:(void (^)(NSError *error))handler {
[self setTokeToRequest:request];
}
- (void)setTokeToRequest:(NSMutableURLRequest *)request {
if (request) {
NSString *value = [NSString stringWithFormat:@"%s %@", GTM_OAUTH2_BEARER, self.accessToken];
[request setValue:value forHTTPHeaderField:@"Authorization"];
}
}
- (BOOL)isAuthorizedRequest:(NSURLRequest *)request {
return NO;
}
- (void)stopAuthorization {
}
- (void)stopAuthorizationForRequest:(NSURLRequest *)request {
}
- (BOOL)isAuthorizingRequest:(NSURLRequest *)request {
return YES;
}
@end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With