{"id":13906431,"url":"https://github.com/google/GTMAppAuth","last_synced_at":"2025-07-18T04:31:25.156Z","repository":{"id":37580279,"uuid":"65241557","full_name":"google/GTMAppAuth","owner":"google","description":"Apple platforms SDK for using AppAuth with Google libraries.","archived":false,"fork":false,"pushed_at":"2024-08-06T18:22:47.000Z","size":342,"stargazers_count":376,"open_issues_count":30,"forks_count":197,"subscribers_count":77,"default_branch":"master","last_synced_at":"2024-10-29T20:33:20.296Z","etag":null,"topics":["authorization-flow","oauth-client"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/google.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-08-08T21:42:48.000Z","updated_at":"2024-10-25T11:27:43.000Z","dependencies_parsed_at":"2023-02-11T22:45:14.180Z","dependency_job_id":"9b533ca6-404d-48da-bc8a-c6c07d1a7853","html_url":"https://github.com/google/GTMAppAuth","commit_stats":{"total_commits":163,"total_committers":17,"mean_commits":9.588235294117647,"dds":0.6380368098159509,"last_synced_commit":"77d9408316cc8a4a181435fdfb9b6bdc450efc38"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2FGTMAppAuth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2FGTMAppAuth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2FGTMAppAuth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2FGTMAppAuth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/google","download_url":"https://codeload.github.com/google/GTMAppAuth/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225773956,"owners_count":17522097,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization-flow","oauth-client"],"created_at":"2024-08-06T23:01:35.586Z","updated_at":"2025-07-18T04:31:25.145Z","avatar_url":"https://github.com/google.png","language":"Swift","funding_links":[],"categories":["HarmonyOS"],"sub_categories":["Windows Manager"],"readme":"[![Version](https://img.shields.io/cocoapods/v/GTMAppAuth.svg?style=flat)](https://cocoapods.org/pods/GTMAppAuth)\n[![Platform](https://img.shields.io/cocoapods/p/GTMAppAuth.svg?style=flat)](https://cocoapods.org/pods/GTMAppAuth)\n[![License](https://img.shields.io/cocoapods/l/GTMAppAuth.svg?style=flat)](https://cocoapods.org/pods/GTMAppAuth)\n[![tests](https://github.com/google/GTMAppAuth/actions/workflows/tests.yml/badge.svg?event=push)](https://github.com/google/GTMAppAuth/actions/workflows/tests.yml)\n\n# GTMAppAuth for Apple Platforms\n\nGTMAppAuth enables you to use [AppAuth](https://github.com/openid/AppAuth-iOS)\nwith the\n[Google Toolbox for Mac - Session Fetcher](https://github.com/google/gtm-session-fetcher)\nand\n[Google APIs Client Library for Objective-C For REST](https://github.com/google/google-api-objectivec-client-for-rest)\nlibraries on iOS, macOS, tvOS, and watchOS by providing an implementation of\n[`GTMFetcherAuthorizationProtocol`](https://github.com/google/gtm-session-fetcher/blob/2a3b5264108e80d62003b770ff02eb7364ff1365/Source/GTMSessionFetcher.h#L660)\nfor authorizing requests with AppAuth.\n\nGTMAppAuth is an alternative authorizer to [GTMOAuth2](https://github.com/google/gtm-oauth2)\n. The key differentiator is the use of the user's default browser for the\nauthorization, which is more secure, more usable (the user's session can be\nreused) and follows modern OAuth [best practices for native apps](https://datatracker.ietf.org/doc/html/rfc8252).\nCompatibility methods for GTMOAuth2 are offered allowing you to migrate\nfrom GTMOAuth2 to GTMAppAuth preserving previously serialized authorizations\n(so users shouldn't need to re-authenticate).\n\n## Setup\n\nIf you use [CocoaPods](https://guides.cocoapods.org/using/getting-started.html),\nsimply add:\n\n    pod 'GTMAppAuth'\n\nTo your `Podfile` and run `pod install`.\n\n## Usage\n\n### Configuration\n\nTo configure GTMAppAuth with the OAuth endpoints for Google, you can use the\nconvenience method:\n\n```objc\nOIDServiceConfiguration *configuration = [GTMAuthSession configurationForGoogle];\n```\n\nAlternatively, you can configure GTMAppAuth by specifying the endpoints\ndirectly:\n\n```objc\nNSURL *authorizationEndpoint =\n    [NSURL URLWithString:@\"https://accounts.google.com/o/oauth2/v2/auth\"];\nNSURL *tokenEndpoint =\n    [NSURL URLWithString:@\"https://www.googleapis.com/oauth2/v4/token\"];\n\nOIDServiceConfiguration *configuration =\n    [[OIDServiceConfiguration alloc]\n        initWithAuthorizationEndpoint:authorizationEndpoint\n                        tokenEndpoint:tokenEndpoint];\n\n// perform the auth request...\n```\n\nOr through discovery:\n\n```objc\nNSURL *issuer = [NSURL URLWithString:@\"https://accounts.google.com\"];\n\n[OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer\n    completion:^(OIDServiceConfiguration *_Nullable configuration,\n                 NSError *_Nullable error) {\n  if (!configuration) {\n    NSLog(@\"Error retrieving discovery document: %@\",\n          [error localizedDescription]);\n    return;\n  }\n\n  // perform the auth request...\n}];\n```\n\n### Authorizing\n\nFirst, you need to have a way for your UIApplicationDelegate to continue the\nauthorization flow session from the incoming redirect URI. Typically you could\nstore the in-progress OIDAuthorizationFlowSession instance in a property:\n\n```objc\n// property of the app's UIApplicationDelegate\n@property(nonatomic, nullable)\n    id\u003cOIDExternalUserAgentSession\u003e currentAuthorizationFlow;\n```\n\nAnd in a location accessible by all controllers that need authorization, a\nproperty to store the authorization state:\n\n```objc\n// property of the containing class\n@property(nonatomic, nullable) GTMAuthSession *authSession;\n```\n\nThen, initiate the authorization request. By using the\n`authStateByPresentingAuthorizationRequest` method, the OAuth token\nexchange will be performed automatically, and everything will be protected with\nPKCE (if the server supports it).\n\n```objc\n// builds authentication request\nOIDAuthorizationRequest *request =\n    [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration\n                                                  clientId:kClientID\n                                              clientSecret:kClientSecret\n                                                    scopes:@[OIDScopeOpenID, OIDScopeProfile]\n                                               redirectURL:redirectURI\n                                              responseType:OIDResponseTypeCode\n                                      additionalParameters:nil];\n// performs authentication request\nself.appDelegate.currentAuthorizationFlow =\n    [OIDAuthState authStateByPresentingAuthorizationRequest:request\n        callback:^(OIDAuthState *_Nullable authState,\n                   NSError *_Nullable error) {\n  if (authState) {\n    // Creates a GTMAuthSession from the OIDAuthState.\n    self.authSession = [[GTMAuthSession alloc] initWithAuthState:authState];\n    NSLog(@\"Got authorization tokens. Access token: %@\",\n          authState.lastTokenResponse.accessToken);\n  } else {\n    NSLog(@\"Authorization error: %@\", [error localizedDescription]);\n    self.authSession = nil;\n  }\n}];\n```\n\n### Handling the Redirect\n\nThe authorization response URL is returned to the app via the platform-specific\napplication delegate method, so you need to pipe this through to the current\nauthorization session (created in the previous session).\n\n#### macOS Custom URI Scheme Redirect Example\n\n```objc\n- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {\n  // Other app initialization code ...\n\n  // Register for GetURL events.\n  NSAppleEventManager *appleEventManager =\n      [NSAppleEventManager sharedAppleEventManager];\n  [appleEventManager setEventHandler:self\n                         andSelector:@selector(handleGetURLEvent:withReplyEvent:)\n                       forEventClass:kInternetEventClass\n                          andEventID:kAEGetURL];\n}\n\n- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event\n           withReplyEvent:(NSAppleEventDescriptor *)replyEvent {\n  NSString *URLString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];\n  NSURL *URL = [NSURL URLWithString:URLString];\n  [_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:URL];\n}\n```\n\n#### iOS Custom URI Scheme Redirect Example\n\n```objc\n- (BOOL)application:(UIApplication *)app\n            openURL:(NSURL *)url\n            options:(NSDictionary\u003cNSString *, id\u003e *)options {\n  // Sends the URL to the current authorization flow (if any) which will\n  // process it if it relates to an authorization response.\n  if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) {\n    _currentAuthorizationFlow = nil;\n    return YES;\n  }\n\n  // Your additional URL handling (if any) goes here.\n\n  return NO;\n}\n```\n\n### Making API Calls\n\nThe goal of GTMAppAuth is to enable you to authorize HTTP requests with fresh\ntokens following the Session Fetcher pattern, which you can do like so:\n\n```objc\n// Creates a GTMSessionFetcherService with the authorization.\n// Normally you would save this service object and re-use it for all REST API calls.\nGTMSessionFetcherService *fetcherService = [[GTMSessionFetcherService alloc] init];\nfetcherService.authorizer = self.authSession;\n\n// Creates a fetcher for the API call.\nNSURL *userinfoEndpoint = [NSURL URLWithString:@\"https://www.googleapis.com/oauth2/v3/userinfo\"];\nGTMSessionFetcher *fetcher = [fetcherService fetcherWithURL:userinfoEndpoint];\n[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {\n  // Checks for an error.\n  if (error) {\n    // OIDOAuthTokenErrorDomain indicates an issue with the authorization.\n    if ([error.domain isEqual:OIDOAuthTokenErrorDomain]) {\n      self.authSession = nil;\n      NSLog(@\"Authorization error during token refresh, clearing state. %@\",\n            error);\n    // Other errors are assumed transient.\n    } else {\n      NSLog(@\"Transient error during token refresh. %@\", error);\n    }\n    return;\n  }\n\n  // Parses the JSON response.\n  NSError *jsonError = nil;\n  id jsonDictionaryOrArray =\n      [NSJSONSerialization JSONObjectWithData:data options:0 error:\u0026jsonError];\n\n  // JSON error.\n  if (jsonError) {\n    NSLog(@\"JSON decoding error %@\", jsonError);\n    return;\n  }\n\n  // Success response!\n  NSLog(@\"Success: %@\", jsonDictionaryOrArray);\n}];\n```\n\n### Saving to the Keychain\n\nYou can easily save `GTMAuthSession` instances to the Keychain using the `GTMKeychainStore` class.\n\n```objc\n// Create a GIDKeychainStore instance, intializing it with the Keychain item name `kKeychainItemName`\n// which will be used when saving, retrieving, and removing `GTMAuthSession` instances.\nGIDKeychainStore *keychainStore = [[GIDKeychainStore alloc] initWithItemName:kKeychainItemName];\n    \nNSError *error;\n\n// Save to the Keychain\n[keychainStore saveAuthSession:self.authSession error:\u0026error];\nif (error) {\n  // Handle error\n}\n\n// Retrieve from the Keychain\nself.authSession = [keychainStore retrieveAuthSessionWithError:\u0026error];\nif (error) {\n  // Handle error\n}\n\n// Remove from the Keychain\n[keychainStore removeAuthSessionWithError:\u0026error];\nif (error) {\n  // Handle error\n}\n```\n\n#### Keychain Storage\n\nWith `GTMKeychainStore`, by default, `GTMAuthSession` instances are stored using Keychain items of the\n[`kSecClassGenericPassword`](https://developer.apple.com/documentation/security/ksecclassgenericpassword?language=objc)\nclass with a [`kSecAttrAccount`](https://developer.apple.com/documentation/security/ksecattraccount?language=objc)\nvalue of \"OAuth\" and a developer supplied value for [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice?language=objc).\nFor this use of generic password items, the combination of account and service\nvalues acts as the\n[primary key](https://developer.apple.com/documentation/security/1542001-security_framework_result_codes/errsecduplicateitem?language=objc)\nof the Keychain items.  The\n[`kSecAttrAccessible`](https://developer.apple.com/documentation/security/ksecattraccessible?language=objc)\nkey is set to\n[`kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`](https://developer.apple.com/documentation/security/ksecattraccessibleafterfirstunlockthisdeviceonly?language=objc)\nin order to allow background access after initial device unlock following a\nrestart.  A [keyed archive](https://developer.apple.com/documentation/foundation/nskeyedarchiver?language=objc)\nrepresentation of the relevant `GTMAuthSession` instance is supplied as the value for\n[`kSecValueData`](https://developer.apple.com/documentation/security/ksecvaluedata?language=objc)\nand this is encrypted and stored by\n[Keychain Services](https://developer.apple.com/documentation/security/keychain_services?language=objc).\n\n##### macOS\n\nFor macOS, two Keychain storage options are available: the traditional file-based Keychain storage\nwhich uses access control lists and the more modern [data protection Keychain storage](https://developer.apple.com/documentation/security/ksecusedataprotectionkeychain?language=objc)\nwhich uses Keychain access control groups. By default, GTMAppAuth follows [Apple's advice](https://developer.apple.com/documentation/security/ksecusedataprotectionkeychain?language=objc) \nto use the data protection Keychain storage on macOS.  You may opt into using file-based Keychain \nstorage by including the `GTMKeychainAttribute.useFileBasedKeychain` attribute in the \n`keychainAttributes` parameter of `initWithItemName:keychainAttributes:` when initializing \n`GTMKeychainStore`. Note that Keychain items stored via one storage type will not be available via \nthe other. macOS apps that use the data protection Keychain without specifying an access group will \nneed to be include `$(AppIdentifierPrefix)$(CFBundleIdentifier)` as a keychain access group in their\napps entitlements for Keychain operations to succeed.\n\n###### Migrating to data protected keychain storage\n\nPrior to version 5.0.0 GTMAppAuth defaulted to using the file-based Keychain. It is recommended that \ndevelopers migrate to the data protection Keychain. \n\nExample migration:\n```swift\n// Create a keychain store with data protection.\nGTMKeychainStore dataProtectionKeychainStore = [[GTMKeychainStore alloc] \n                                                    initWithItemName:kKeychainName];\n\n// Attempt to retrieve from Keychain.\nNSError *error;\nGTMAuthSession *authSession = [keychainStore retrieveAuthSessionWithError:\u0026error];\n\n// If no authSession found, try to retrieve from file-based Keychain.\nif (!authSession) {\n  GTMKeychainAttribute *fileBased = [GTMKeychainAttribute useFileBasedKeychain];\n  NSSet *attributes = [NSSet setWithArray:@[fileBased]];\n  GTMKeychainStore fileBasedKeychainStore = [[GTMKeychainStore alloc] \n                                                  initWithItemName:kExampleAuthorizerKey\n                                                  keychainAttributes:attributes];\n  authSession =[keychainStore retrieveAuthSessionWithError:\u0026error];\n  \n  if (authSession) {\n    // Remove previously stored session from file-based Keychain.\n    [fileBasedKeychainStore removeAuthSessionWithError:\u0026error];\n    // Save to the data protected Keychain.\n    [dataProtectionKeychainStore saveAuthSession:authSession error:\u0026error];\n  }\n}\n```\n\n### Implementing Your Own Storage\n\nIf you'd like to use a backing store other than the Keychain to save your `GTMAuthSession`\ninstances, you can create your own `GTMAuthSessionStore` conformance.  Use `GTMKeychainStore` as an\nexample of how to do this.\n\n#### GTMOAuth2 Compatibility\n\nTo assist the migration from GTMOAuth2 to GTMAppAuth, GTMOAuth2-compatible Keychain methods are provided in `GTMKeychainStore`.\n\n```objc\nGTMKeychainStore keychainStore = [[GTMKeychainStore alloc] initWithItemName:kKeychainItemName];\n\n// Retrieve from the Keychain\nNSError *error;\nGTMAuthSession *authSession =\n    [keychainStore retrieveAuthSessionForGoogleInGTMOAuth2FormatWithClientID:clientID\n                                                                clientSecret:clientSecret\n                                                                       error:\u0026error];\n\n// Remove from the Keychain\n[keychainStore removeAuthSessionWithError:\u0026error];\n```\n\nYou can also save to GTMOAuth2 format, though this is discouraged (you\nshould save in GTMAppAuth format as described above).\n\n```objc\n// Save to the Keychain\n[keychainStore saveWithGTMOAuth2FormatForAuthSession:authSession error:\u0026error];\n```\n\n## Included Samples\n\nTry out one of the included sample apps under [Examples](Examples). In the\napps folder run `pod install`, then open the resulting `xcworkspace` file.\n\nBe sure to follow the instructions in\n[Example-iOS/README.md](Examples/Example-iOS/README.md) or\n[Example-macOS/README.md](Examples/Example-macOS/README.md) to configure\nyour own OAuth client ID for use with the example.\n\n## Differences with GTMOAuth2\n\n### Authorization Method\n\nGTMAppAuth uses the browser to present the authorization request, while\nGTMOAuth2 uses an embedded web-view. Migrating to GTMAppAuth will require you\nto change how you authorize the user. Follow the instructions above to get the\nauthorization.  You can then create a `GTMAuthSession` object with its\n`initWithAuthState:` initializer.  Once you have a `GTMAuthSession` you can\ncontinue to make REST calls as before.\n\n### Error Handling\n\nGTMAppAuth's error handling is also different. There are no notifications,\ninstead you need to inspect NSError in the callback. If the error domain is\n`OIDOAuthTokenErrorDomain`, it indicates an authorization error, you should\nclear your authorization state and consider prompting the user to authorize\nagain.  Other errors are generally considered transient, meaning that you should\nretry the request after a delay.\n\n### Serialization\n\nThe serialization format is different between GTMOAuth2 and GTMAppAuth, though\nwe have methods to help you migrate from one to the other without losing any\ndata.\n\n## Migrating from GTMOAuth2\n\n### OAuth Client Registration\n\nTypically, GTMOAuth2 clients are registered with Google as type \"Other\". Instead, Apple clients should be registered with the type \"iOS\".\n\nIf you're migrating an Apple client in the *same project as your existing client*,\n[register a new iOS client](https://console.developers.google.com/apis/credentials?project=_)\nto be used with GTMAppAuth.\n\n### Changing your Authorization Flows\n\nBoth GTMOAuth2 and GTMAppAuth support the `GTMFetcherAuthorizationProtocol`\nallowing you to use the authorization with the session fetcher.  Where you\npreviously had a property like `GTMOAuth2Authentication *authorization` change the\ntype to reference the protocol instead, i.e.:\n`id\u003cGTMFetcherAuthorizationProtocol\u003e authorization`.  This allows you to switch\nthe authorization implementation under the hood to GTMAppAuth.\n\nThen, follow the instructions above to replace authorization request\n(where you ask the user to grant access) with the GTMAppAuth approach. If you\ncreated a new OAuth client, use that for these requests.\n\n### Serialization \u0026 Migrating Existing Grants\n\nGTMAppAuth has a new data format and APIs for serialization. Unlike\nGTMOAuth2, GTMAppAuth serializes the configuration and history of the\nauthorization, including the client id, and a record of the authorization\nrequest that resulted in the authorization grant.\n\nThe client ID used for GTMAppAuth is [different](#oauth-client-registration) to\nthe one used for GTMOAuth2. In order to keep track of the different client ids\nused for new and old grants, it's recommended to migrate to the new\nserialization format, which will store that for you. \n[GTMOAuth2-compatible serialization](#gtmoauth2-compatibility) is\nalso offered, but not fully supported.\n\nChange how you serialize your `authorization` object by using `GTMAuthSession` and `GTMKeychainStore` as follows:\n\n```objc\n// Create an auth session from AppAuth's auth state object\nGTMAuthSession *authSession = [[GTMAuthSession alloc] initWithAuthState:authState];\n\n// Create a keychain store\nGTMKeychainStore keychainStore = [[GTMKeychainStore alloc] initWithItemName:kNewKeychainName];\n\n// Serialize to Keychain\nNSError *error;\n[keychainStore saveAuthSession:authSession error:\u0026error];\n```\n\nBe sure to use a *new* name for the keychain. Don't reuse your old one!\n\nFor deserializing, we can preserve all existing grants (so users who authorized\nyour app in GTMOAuth2 don't have to authorize it again). Remember that when\ndeserializing the *old* data you need to use your *old* keychain name, and\nthe old client id and client secret (if those changed), and that when \nserializing to the *new* format, use the *new* keychain name.\nOnce again, pay particular care to use the old details when deserializing the\nGTMOAuth2 keychain, and the new details for all other GTMAppAuth calls.\n\nKeychain migration example:\n\n```objc\n// Create a keychain store\nGTMKeychainStore keychainStore = [[GTMKeychainStore alloc] initWithItemName:kNewKeychainName];\n\n// Attempt to deserialize from Keychain in GTMAppAuth format.\nNSError *error;\nGTMAuthSesion *authSession =\n    [keychainStore retrieveAuthSessionWithError:\u0026error];\n\n// If no data found in the new format, try to deserialize data from GTMOAuth2\nif (!authSession) {\n  // Tries to load the data serialized by GTMOAuth2 using old keychain name.\n  // If you created a new client id, be sure to use the *previous* client id and secret here.\n  GTMKeychainStore oldKeychainStore = [[GTMKeychainStore alloc] initWithItemName:kPreviousKeychainName];\n  authSession =\n      [oldKeychainStore retrieveAuthSessionInGTMOAuth2FormatWithClientID:kPreviousClientID\n                                                            clientSecret:kPreviousClientSecret\n                                                                   error:\u0026error];\n  if (authSession) {\n    // Remove previously stored GTMOAuth2-formatted data.\n    [oldKeychainStore removeAuthSessionWithError:\u0026error];\n    // Serialize to Keychain in GTMAppAuth format.\n    [keychainStore saveAuthSession:authSession error:\u0026error];\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2FGTMAppAuth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoogle%2FGTMAppAuth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2FGTMAppAuth/lists"}