When I try to login to YouTube and upload a video, it gets uploaded without any issue. If I upload a video after 2-3 hours, I will get an error saying,
Error: Error Domain=com.google.GTLJSONRPCErrorDomain Code=401 "The operation couldn’t be completed. (Invalid Credentials)" UserInfo=0x14585d90 {error=Invalid Credentials, GTLStructuredError=GTLErrorObject 0x14d85ba0: {message:"Invalid Credentials" code:401 data:[1]}, NSLocalizedFailureReason=(Invalid Credentials)}
Here is the code which does Youtube login,
GIDSignIn *googleSignIn = [GDSharedInstance googleSDKApplicationSharedInstance];
googleSignIn.delegate = self;
googleSignIn.scopes = [NSArray arrayWithObject:#"https://www.googleapis.com/auth/youtube"];
[googleSignIn signIn];
signin delegate
- (void)signIn:(GIDSignIn *)signIn didSignInForUser:(GIDGoogleUser *)user withError:(NSError *)error
{
// Auth is converted to use it for uploading a video
GTMOAuth2Authentication *youTubeAuth = [[GTMOAuth2Authentication alloc] init];
youTubeAuth.clientID = kClientID;
youTubeAuth.clientSecret = #"xxx";
youTubeAuth.userEmail = googleUser.profile.email;
youTubeAuth.userID = googleUser.userID;
youTubeAuth.accessToken = googleUser.authentication.accessToken;
youTubeAuth.refreshToken = googleUser.authentication.refreshToken;
youTubeAuth.expirationDate = googleUser.authentication.accessTokenExpirationDate;
self.youTubeService.authorizer = youTubeAuth;
}
Upload code,
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:[NSURL URLWithString:path] error:&error];
if (fileHandle) {
NSString *mimeType = [self MIMETypeForFilename:filename
defaultMIMEType:#"video/mov"];
GTLUploadParameters *uploadParameters =
[GTLUploadParameters uploadParametersWithFileHandle:fileHandle
MIMEType:mimeType];
uploadParameters.uploadLocationURL = locationURL;
GTLQueryYouTube *query = [GTLQueryYouTube queryForVideosInsertWithObject:video
part:#"snippet,status,recordingDetails"
uploadParameters:uploadParameters];
GTLServiceYouTube *service = self.youTubeService;
self.uploadFileTicket = [service executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLYouTubeVideo *uploadedVideo,
NSError *error)
{
// here I will get 401 error
}];
}
The only problem is with the GTLServiceYouTube. GIDSignIn seems to handle the refresh tokens, so that the user is always logged in after the first login. But the GTLOAuth2Authentication only works on the first login and is broken after one hour.
The token needs to be refreshed.
Use this piece of code :-
- (void)applicationWillEnterForeground:(UIApplication *)application {
[[GIDSignIn sharedInstance] signInSilently]
}
The problem here is that your auth token is expiring. You will have to use your refresh token to get a new, valid auth token after your old token expires.
If you are using an older version of the Google Plus iOS SDK, You can use GTMOAuth2Authentication to force a refresh of the auth token with the authorizeRequest: method.
From GTMOAuth2Authentication.h
// The request argument may be nil to just force a refresh of the access token,
// if needed.
- (void)authorizeRequest:(NSMutableURLRequest *)request
completionHandler:(void (^)(NSError *error))handler;
Implementation:
// In your sign in method
[[GPPSignIn sharedInstance] setKeychainName:#"googleAuth"];
// ...
// Retrieving auth and refreshing token
GTMOAuth2Authentication *auth;
auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:#"googleAuth"
clientID:#"kYourGoogleClientId"
clientSecret:#"kYourGoogleClientSecret"];
NSLog(#"old auth: %#", auth);
[auth authorizeRequest:nil completionHandler:^(NSError *error) {
if (error) {
// no auth data or refresh failed
NSLog(#"Error: %#", error);
} else {
// Auth token refresh successful
NSLog(#"new auth: %#", auth);
}
}];
Related
I'm a newbie on mobile dev. I'm trying to authenticate to Amazon Cognito.
I first login to Credentials Provider using a username, pin, platform and deviceToken using custom services model - I then get identityId, endPoint and token back. I'm told that I need to swap the token I got back and refresh my credentials in order for me to be authenticated to AWS Cognito and S3. But all the process is confusing and have a lot of examples that are different.
I've created a SignInProvider, extending AWSSignInProvider to access the - (void) login: (void (^) (id result, NSError *error)) completionHanlder; I have my token, endpoint and identityId inside my login method..what do I do with the completion handler and whats next after.
#implementation SignInProvider
+(instanceType) sharedInstance{}
- (NSString) identityProviderName{}
- (AWSTask<NSString*>*) token{}
- (BOOL) isLoggedIn{}
- (NSSting*) userName{}
- (void) reloadSession{}
- (void) login: (void (^) (id result, NSError *error)) completionHanlder{
authRequest = [IMPCLDMobileAuthenticationRequest new];
[authRequest setToken:#"930fc1b56d8ca19a84500f9a79af71b65f60331f0242ce4395cdf41186443692"];
[authRequest setPassword:#"pin"];
[authRequest setUsername:#"example#email.co.za"];
[authRequest setPlatform:#"ios"];
serviceClient = [IMPCLDImpressionInternalMicroserviceClient defaultClient];
[[serviceClient mobileAuthenticationPost:authRequest] continueWithBlock:^id(AWSTask *loginTask)
{
//what to do here with my loginTask results (token, endpoint, identityId)
}
return nil;
}
To swap/save token in AWS you need to do below in your continueWithBlock
[[serviceClient mobileAuthenticationPost:authRequest] continueWithBlock:^id(AWSTask *loginTask)
{
AWSSNSCreateEndpointResponse *response = loginTask.result;
AWSSNSSubscribeInput *subscribeRequest = [AWSSNSSubscribeInput new];
subscribeRequest.endpoint = response.endpointArn;
subscribeRequest.protocols = #"application";
subscribeRequest.topicArn = YOUR_TOPIC_ARN;
return [sns subscribe:subscribeRequest];
}] continueWithBlock:^id(AWSTask *task) {
if (task.cancelled) {
NSLog(#"Task cancelled");
}
else if (task.error) {
NSLog(#"Error occurred: [%#]", task.error);
}
else {
NSLog(#"Success");
}
return nil;
}];
In my app, I need to add my authentication token in the HTTPHeadField for every NSURLRequest API call that I make to my server. This token is only valid for 2 days. When it becomes invalid, I'll receive a "token_invalid" error response from my server, meaning that I'll need to send an API call to my server to refresh my auth token.
The problem that's hard to wrap my head around is that these NSURLRequests are done concurrently, so when each fails due to an expired token, ALL of them are going to attempt to refresh the token. How do I set this up so that the token is refreshed ONCE, and when that's done, re-attempt all the failed requests?
PROGRESS
What I have so far works, but only to a certain extent that confuses me. When I successfully refresh the auth token, I iterate through all the failed requests, and re-attempt them. However, all of them are being re-attempted in that ONE API call that was responsible for refreshing the auth token.
For example, 3 API calls are being made (Friend Requests, Notifications, and Getting a User's Friends). If the "Get Friend Requests" API call fails first, it's responsible for refreshing the token. The other two API requests are put in the failedRequests array. When the auth token is successfully refreshed, only the "Get Friend Request" API call's success block is being passed through...3 TIMES!
I kinda understand why it's doing that, because I'm re-attempting all the failed API requests in the context of one NSURLRequest's sendTask method. Is there a way for me to re-attempt the failed requests in their given contexts when the auth token is refreshed in the kind of way that Key-Value Observing works?
-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
MyAPIInterface *__weak weakSelf = self;
[self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
{
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 401) {
if ([error isEqualToString:#"invalid_credentials"]) {
errorCallback(#"Invalid username and/or password");
}
else if ([error isEqualToString:#"token_expired"]) {
// check if request's auth token differs from api's current auth token
NSArray *requestHeaderValueComponents = [[request valueForHTTPHeaderField:#"Authorization"] componentsSeparatedByString:#" "];
NSString *requestAuthToken = requestHeaderValueComponents[1];
// if new auth token hasn't been retrieved yet
if ([requestAuthToken isEqualToString:weakSelf.authToken]) {
NSLog(#"THE AUTH TOKENS ARE EQUAL");
if (!weakSelf.currentlyRefreshingToken.boolValue) {
//lock alreadyRefreshingToken boolean
weakSelf.currentlyRefreshingToken = [NSNumber numberWithBool:YES];
NSLog(#"NOT REFRESHING TOKEN");
// add mutable failed request (to change auth token header later) to failedRequests array
NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
NSMutableURLRequest *mutableFailedRequest = [request mutableCopy];
[mutableFailedRequests addObject:mutableFailedRequest];
weakSelf.failedRequests = [mutableFailedRequests copy];
// refresh auth token
[weakSelf refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
//store authToken
weakSelf.authToken = response[#"token"];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:weakSelf.authToken forKey:#"authToken"];
[defaults synchronize];
//change auth token http header of each failed request and re-attempt them
for (NSMutableURLRequest *failedRequest in weakSelf.failedRequests) {
NSString *newAuthHeaderValue = [NSString stringWithFormat:#"Bearer %#", weakSelf.authToken];
[failedRequest setValue:newAuthHeaderValue forHTTPHeaderField:#"Authorization"];
[weakSelf sendTask:failedRequest successCallback:success errorCallback:errorCallback];
}
//clear failedRequests array and unlock alreadyRefreshingToken boolean
[weakSelf clearFailedRequests];
weakSelf.currentlyRefreshingToken = [NSNumber numberWithBool:NO];
NSLog(#"TOKEN REFRESHING SUCCESSFUL");
} errorCallback:^(NSString *error) {
NSLog(#"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
//clear failedRequests array
[weakSelf clearFailedRequests];
weakSelf.currentlyRefreshingToken = [NSNumber numberWithBool:NO];
errorCallback(#"Your login session has expired");
}];
}
else {
NSLog(#"ALREADY REFRESHING TOKEN. JUST ADD TO FAILED LIST");
// add mutable failed request (to change auth token header later) to failedRequests array
NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
NSMutableURLRequest *mutableFailedRequest = [request mutableCopy];
[mutableFailedRequests addObject:mutableFailedRequest];
weakSelf.failedRequests = [mutableFailedRequests copy];
}
}
// if new auth token has been retrieved, simply re-attempt request with new auth token
else {
NSMutableURLRequest *failedRequest = [request mutableCopy];
NSString *newAuthHeaderValue = [NSString stringWithFormat:#"Bearer %#", weakSelf.authToken];
[failedRequest setValue:newAuthHeaderValue forHTTPHeaderField:#"Authorization"];
[weakSelf sendTask:failedRequest successCallback:success errorCallback:errorCallback];
}
}
else {
errorCallback(error);
}
}
else {
errorCallback(error);
}
}];
}];
[task resume];
}
1)I think you should be getting the token from successful login to the account.
2)So when ever the token gets expired. Show login screen to user.
3)If user logged in successfully he get new access token.
4) You can use this for your next request
I am writing an iOS app that uses Google's GIDSignIn [1] to sign in users and GTLServiceYoutube to execute queries against Youtube (uploading videos and retrieving Youtube video lists).
This works fine when the user first logs in but after approximately one hour, the access token expires and the user is no longer able to execute queries with GTLServiceYoutube due to a 401 error (invalid credentials).
I use the following code to set the GTMOAuth2Authentication after successful login:
- (void)signIn:(GIDSignIn *)signIn didSignInForUser:(GIDGoogleUser *)user withError:(NSError *)error {
if (error == nil) {
[self setAuthorizerForSignIn:signIn user:user];
}
[super signIn:signIn didSignInForUser:user withError:error];
}
- (void)setAuthorizerForSignIn:(GIDSignIn *)signIn user:(GIDGoogleUser *)user {
GTMOAuth2Authentication *auth = [[GTMOAuth2Authentication alloc] init];
[auth setClientID:signIn.clientID];
[auth setClientSecret:[[NSBundle mainBundle] objectForInfoDictionaryKey:#"GoogleClientSecret"]];
[auth setUserEmail:user.profile.email];
[auth setUserID:user.userID];
[auth setAccessToken:user.authentication.accessToken];
[auth setRefreshToken:user.authentication.refreshToken];
[auth setExpirationDate: user.authentication.accessTokenExpirationDate];
[[UserManager sharedInstance].youTubeService setAuthorizer:auth];
}
where [[UserManager sharedInstance].youTubeService is an instance of GTLServiceYouTube.
The only problem is with the GTLServiceYouTube. GIDSignIn seems to handle the refresh tokens, so that the user is always logged in after the first login. But the GTLOAuth2Authentication only works on the first login and is broken after one hour.
So my question is: Am I doing something wrong here? Or am I missing something to get the proper access token in GTMOAuth2Authentication after refresh?
[1] https://developers.google.com/identity/sign-in/ios/api/interface_g_i_d_sign_in
I believe the correct way to do this is by signing the user back in when the app is reopened or the token needs to be refreshed. This can be done by calling [[GIDSignIn sharedInstance] signInSilently] and then, when it finishes signing in update the keychain or datastore with your new auth tokens.
As of GoogleSignIn 2.1.0, making a call to [GIDSignIn sharedInstance].signInSilently; updates the credentials stored in [GIDSignIn sharedInstance].currentUser.authentication.
Run pod update on your project to update to the 2.1.0 SDK if you're using Cocoapods.
With GTMOAuth2Authentication you can force a refresh of the auth token with the authorizeRequest: method.
From GTMOAuth2Authentication.h
// The request argument may be nil to just force a refresh of the access token,
// if needed.
- (void)authorizeRequest:(NSMutableURLRequest *)request
completionHandler:(void (^)(NSError *error))handler;
Implementation:
// In your sign in method
[[GPPSignIn sharedInstance] setKeychainName:#"googleAuth"];
// ...
// Retrieving auth and refreshing token
GTMOAuth2Authentication *auth;
auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:#"googleAuth"
clientID:#"kYourGoogleClientId"
clientSecret:#"kYourGoogleClientSecret"];
NSLog(#"old auth: %#", auth);
[auth authorizeRequest:nil completionHandler:^(NSError *error) {
if (error) {
// no auth data or refresh failed
NSLog(#"Error: %#", error);
} else {
// Auth token refresh successful
NSLog(#"new auth: %#", auth);
}
}];
I'm trying to use Google's OAuth2 and YouTube APIs. The OAuth returns GTMOAuth2Authentication object that you then use to make requests to services like YouTube. My login works fine, and when I manually pass the authentication object, I can make requests.
However, I should also be able to access the authentication object via keychain, and I receive a valid object, but if I try to use it to make requests, I cannot. I keep getting the following error: "The operation couldn’t be completed. (com.google.GTMHTTPFetcher error -1.)" I'd appreciate it if someone could point out my mistake. I'm testing on a real iPhone 5s.
Authentication Code:
GTMOAuth2ViewControllerTouch * viewController = [[GTMOAuth2ViewControllerTouch alloc]
initWithScope:scope
clientID:kGoogleClientID
clientSecret:kGoogleClientSecret
keychainItemName:kGoogleKeychainItemName
delegate:self
finishedSelector:#selector(viewController:
finishedWithAuth:
error:)];
[self.navigationController pushViewController:viewController animated:YES];
Authentication Completion Handler:
- (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
finishedWithAuth:(GTMOAuth2Authentication *)auth
error:(NSError *)error {
NSLog(#"%s", __PRETTY_FUNCTION__);
if (error != nil) {
NSLog(#"%s %#", __PRETTY_FUNCTION__, error.localizedDescription);
return;
}
MediaGETWrapper *getWrapper = [MediaGETWrapper sharedWrapper];
getWrapper.googleAuth = auth; // passing auth directly without keychain
[getWrapper youTubeSubscriptionsWithSuccess:nil failure:nil];
YouTube Client Initialization:
self.youTube = [GTLServiceYouTube new];
GTMOAuth2Authentication *auth = [GTMOAuth2Authentication new];
[GTMOAuth2ViewControllerTouch
authorizeFromKeychainForName:kGoogleKeychainItemName
authentication:auth
error:nil];
self.youTube.authorizer = auth;
Request:
- (void)youTubeSubscriptionsWithSuccess:(void(^)(NSArray *subscriptions))success
failure:(void(^)(NSError *error))error {
NSLog(#"%s", __PRETTY_FUNCTION__);
// self.youTube.authorizer = self.googleAuth; // If uncommented, works!
GTLQueryYouTube *query = [GTLQueryYouTube queryForSubscriptionsListWithPart:#"snippet"];
query.mine = YES;
[self.youTube
executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLYouTubeChannelListResponse *channelList,
NSError *error) {
if (error != nil) {
NSLog(#"%s %#", __PRETTY_FUNCTION__, error.localizedDescription); // fails here
return;
}
for (GTLYouTubeSubscription *channel in channelList) {
NSLog(#"%#", channel.snippet.title);
}
}];
}
I couldn't find a direct fix, but you can do this:
Get the access token from a GTMOAuth2Authentication object via:
auth.accessToken
Then set the access token wherever you want to make requests.
To refresh the token, use this method:
[auth
authorizeRequest:nil // just to refresh
completionHandler:^(NSError *error) {
// your code here
}];
I have added youtube api v3 using google api's objective-C client in my app to upload video to youtube. Testers of the app (in different country) reports that they are unable to upload video to youtube. Video uploading fails after reaching to 100 % progress with backend error. Where as I am not facing that issue in my end here in India. Testers also confirms that youtube video upload is working fine when uploaded using youtube ios app or some different app. They also tried uploading videos from multiple accounts but with same result.
The error log from device console is:
Error Domain=com.google.GTLJSONRPCErrorDomain Code=-32099 "The operation couldn’t be completed. (Backend Error)" UserInfo=0x2438c380 {error=Backend Error, GTLStructuredError=GTLErrorObject 0x27ea3990: {message:"Backend Error" code:-32099 data:[1]}, NSLocalizedFailureReason=(Backend Error)}
and my code that I am using to upload video to youtube is:
GTMOAuth2Authentication *auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:YoutubeOAuthKeyChain clientID:GoogleAPIClientID clientSecret:GoogleAPIClientSecret];
if (!auth) {
[self signInToGoogle];
}else{
if ([auth canAuthorize] && auth.userEmail) {
//Force the api to refresh access token if needed
[auth authorizeRequest:Nil completionHandler:^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!error) {
NSLog(#"Youtube: App authorized. Uploading video now");
self.youTubeService.authorizer = auth;
GTLYouTubeVideoStatus *status = [GTLYouTubeVideoStatus object];
status.privacyStatus = #"public";
GTLYouTubeVideoSnippet *snippet = [GTLYouTubeVideoSnippet object];
snippet.title = _captionTextView.text;
snippet.descriptionProperty = #"This is a test video";
GTLYouTubeVideo *video = [GTLYouTubeVideo object];
video.status = status;
video.snippet = snippet;
NSString *filename = [_moviePath lastPathComponent];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:_moviePath];
if (fileHandle) {
NSString *mimeType = [self MIMETypeForFilename:filename
defaultMIMEType:#"video/mp4"];
GTLUploadParameters *uploadParameters = [GTLUploadParameters uploadParametersWithFileHandle:fileHandle MIMEType:mimeType];
uploadParameters.uploadLocationURL = nil;
//uploadParameters.shouldSendUploadOnly = YES;
GTLQueryYouTube *query = [GTLQueryYouTube queryForVideosInsertWithObject:video part:#"snippet,status" uploadParameters:uploadParameters];
GTLServiceYouTube *service = self.youTubeService;
GTLServiceTicket *ticket = [service executeQuery:query completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!error) {
NSLog(#"Youtube video upload completed ");
}else{
NSLog(#"error completing request with error: %#", error);
}
});
}];
[ticket setUploadProgressBlock:^(GTLServiceTicket *ticket, unsigned long long totalBytesWritten, unsigned long long totalBytesExpectedToWrite) {
float progress = ((float)totalBytesWritten / (float)totalBytesExpectedToWrite) * 100.0f;
NSLog(#"%f %% uploaded");
}];
}
}else{
//Error authorizing the request
NSLog(#"error authorizing request with error: %#", error);
}
});
}];
}else{
//Refresh access token
[self signInToGoogle];
}
}
This issue started just 2 weeks ago. I have no idea if this is a server side issue of some issue with my app. Has anyone also have the same issue?