-
Notifications
You must be signed in to change notification settings - Fork 231
Expand file tree
/
Copy pathGTMAppAuthExampleViewController.m
More file actions
285 lines (237 loc) · 11.1 KB
/
GTMAppAuthExampleViewController.m
File metadata and controls
285 lines (237 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/*! @file AppAuthExampleViewController.m
@brief GTMAppAuth SDK iOS Example
@copyright
Copyright 2016 Google Inc.
@copydetails
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "GTMAppAuthExampleViewController.h"
#import <QuartzCore/QuartzCore.h>
@import AppAuth;
@import GTMAppAuth;
#if __has_include("GTMSessionFetcher/GTMSessionFetcher.h") // Cocoapods
@import GTMSessionFetcher;
#else // SPM
@import GTMSessionFetcherCore;
#endif
#import "AppDelegate.h"
/*! @brief The OIDC issuer from which the configuration will be discovered.
*/
static NSString *const kIssuer = @"https://accounts.google.com";
/*! @brief The OAuth client ID.
@discussion For Google, register your client at
https://console.developers.google.com/apis/credentials?project=_
The client should be registered with the "iOS" type.
*/
static NSString *const kClientID = @"YOUR_CLIENT.apps.googleusercontent.com";
/*! @brief The OAuth redirect URI for the client @c kClientID.
@discussion With Google, the scheme of the redirect URI is the reverse DNS notation of the
client ID. This scheme must be registered as a scheme in the project's Info
property list ("CFBundleURLTypes" plist key). Any path component will work, we use
'oauthredirect' here to help disambiguate from any other use of this scheme.
*/
static NSString *const kRedirectURI =
@"com.googleusercontent.apps.YOUR_CLIENT:/oauthredirect";
/*! @brief @c NSCoding key for the authState property.
*/
static NSString *const kExampleAuthorizerKey = @"authorization";
@interface GTMAppAuthExampleViewController () <OIDAuthStateChangeDelegate,
OIDAuthStateErrorDelegate>
@end
@implementation GTMAppAuthExampleViewController
- (void)viewDidLoad {
[super viewDidLoad];
#if !defined(NS_BLOCK_ASSERTIONS)
// NOTE:
//
// To run this sample, you need to register your own iOS client at
// https://console.developers.google.com/apis/credentials?project=_ and update three configuration
// points in the sample: kClientID and kRedirectURI constants in AppAuthExampleViewController.m
// and the URI scheme in Info.plist (URL Types -> Item 0 -> URL Schemes -> Item 0).
// Full instructions: https://github.com/openid/AppAuth-iOS/blob/master/Example/README.md
NSAssert(![kClientID isEqualToString:@"YOUR_CLIENT.apps.googleusercontent.com"],
@"Update kClientID with your own client ID. "
"Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Example/README.md");
NSAssert(![kRedirectURI isEqualToString:@"com.googleusercontent.apps.YOUR_CLIENT:/oauthredirect"],
@"Update kRedirectURI with your own redirect URI. "
"Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Example/README.md");
// verifies that the custom URI scheme has been updated in the Info.plist
NSArray *urlTypes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"];
NSAssert(urlTypes.count > 0, @"No custom URI scheme has been configured for the project.");
NSArray *urlSchemes = ((NSDictionary *)urlTypes.firstObject)[@"CFBundleURLSchemes"];
NSAssert(urlSchemes.count > 0, @"No custom URI scheme has been configured for the project.");
NSString *urlScheme = urlSchemes.firstObject;
NSAssert(![urlScheme isEqualToString:@"com.googleusercontent.apps.YOUR_CLIENT"],
@"Configure the URI scheme in Info.plist (URL Types -> Item 0 -> URL Schemes -> Item 0) "
"with the scheme of your redirect URI. Full instructions: "
"https://github.com/openid/AppAuth-iOS/blob/master/Example/README.md");
#endif // !defined(NS_BLOCK_ASSERTIONS)
_logTextView.layer.borderColor = [UIColor colorWithWhite:0.8 alpha:1.0].CGColor;
_logTextView.layer.borderWidth = 1.0f;
_logTextView.alwaysBounceVertical = YES;
_logTextView.textContainer.lineBreakMode = NSLineBreakByCharWrapping;
_logTextView.text = @"";
[self loadState];
[self updateUI];
}
/*! @brief Saves the @c GTMAppAuthFetcherAuthorization to @c NSUSerDefaults.
*/
- (void)saveState {
if (_authorization.canAuthorize) {
[GTMAppAuthFetcherAuthorization saveAuthorization:_authorization
toKeychainForName:kExampleAuthorizerKey];
} else {
[GTMAppAuthFetcherAuthorization removeAuthorizationFromKeychainForName:kExampleAuthorizerKey];
}
}
/*! @brief Loads the @c GTMAppAuthFetcherAuthorization from @c NSUSerDefaults.
*/
- (void)loadState {
GTMAppAuthFetcherAuthorization* authorization =
[GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kExampleAuthorizerKey];
[self setGtmAuthorization:authorization];
}
- (void)setGtmAuthorization:(GTMAppAuthFetcherAuthorization*)authorization {
if ([_authorization isEqual:authorization]) {
return;
}
_authorization = authorization;
[self stateChanged];
}
/*! @brief Refreshes UI, typically called after the auth state changed.
*/
- (void)updateUI {
_userinfoButton.enabled = _authorization.canAuthorize;
_clearAuthStateButton.enabled = _authorization.canAuthorize;
// dynamically changes authorize button text depending on authorized state
if (!_authorization.canAuthorize) {
[_authAutoButton setTitle:@"Authorize" forState:UIControlStateNormal];
[_authAutoButton setTitle:@"Authorize" forState:UIControlStateHighlighted];
} else {
[_authAutoButton setTitle:@"Re-authorize" forState:UIControlStateNormal];
[_authAutoButton setTitle:@"Re-authorize" forState:UIControlStateHighlighted];
}
}
- (void)stateChanged {
[self saveState];
[self updateUI];
}
- (void)didChangeState:(OIDAuthState *)state {
[self stateChanged];
}
- (void)authState:(OIDAuthState *)state didEncounterAuthorizationError:(NSError *)error {
[self logMessage:@"Received authorization error: %@", error];
}
- (IBAction)authWithAutoCodeExchange:(nullable id)sender {
NSURL *issuer = [NSURL URLWithString:kIssuer];
NSURL *redirectURI = [NSURL URLWithString:kRedirectURI];
[self logMessage:@"Fetching configuration for issuer: %@", issuer];
// discovers endpoints
[OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer
completion:^(OIDServiceConfiguration *_Nullable configuration, NSError *_Nullable error) {
if (!configuration) {
[self logMessage:@"Error retrieving discovery document: %@", [error localizedDescription]];
[self setGtmAuthorization:nil];
return;
}
[self logMessage:@"Got configuration: %@", configuration];
// builds authentication request
OIDAuthorizationRequest *request =
[[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
clientId:kClientID
scopes:@[OIDScopeOpenID, OIDScopeProfile]
redirectURL:redirectURI
responseType:OIDResponseTypeCode
additionalParameters:nil];
// performs authentication request
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[self logMessage:@"Initiating authorization request with scope: %@", request.scope];
appDelegate.currentAuthorizationFlow =
[OIDAuthState authStateByPresentingAuthorizationRequest:request
presentingViewController:self
callback:^(OIDAuthState *_Nullable authState,
NSError *_Nullable error) {
if (authState) {
GTMAppAuthFetcherAuthorization *authorization =
[[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];
[self setGtmAuthorization:authorization];
[self logMessage:@"Got authorization tokens. Access token: %@",
authState.lastTokenResponse.accessToken];
} else {
[self setGtmAuthorization:nil];
[self logMessage:@"Authorization error: %@", [error localizedDescription]];
}
}];
}];
}
- (IBAction)clearAuthState:(nullable id)sender {
[self setGtmAuthorization:nil];
}
- (IBAction)clearLog:(nullable id)sender {
_logTextView.text = @"";
}
- (IBAction)userinfo:(nullable id)sender {
[self logMessage:@"Performing userinfo request"];
// Creates a GTMSessionFetcherService with the authorization.
// Normally you would save this service object and re-use it for all REST API calls.
GTMSessionFetcherService *fetcherService = [[GTMSessionFetcherService alloc] init];
fetcherService.authorizer = self.authorization;
// Creates a fetcher for the API call.
NSURL *userinfoEndpoint = [NSURL URLWithString:@"https://openidconnect.googleapis.com/v1/userinfo"];
GTMSessionFetcher *fetcher = [fetcherService fetcherWithURL:userinfoEndpoint];
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
// Checks for an error.
if (error) {
// OIDOAuthTokenErrorDomain indicates an issue with the authorization.
if ([error.domain isEqual:OIDOAuthTokenErrorDomain]) {
[self setGtmAuthorization:nil];
[self logMessage:@"Authorization error during token refresh, clearing state. %@", error];
// Other errors are assumed transient.
} else {
[self logMessage:@"Transient error during token refresh. %@", error];
}
return;
}
// Parses the JSON response.
NSError *jsonError = nil;
id jsonDictionaryOrArray =
[NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
// JSON error.
if (jsonError) {
[self logMessage:@"JSON decoding error %@", jsonError];
return;
}
// Success response!
[self logMessage:@"Success: %@", jsonDictionaryOrArray];
}];
}
/*! @brief Logs a message to stdout and the textfield.
@param format The format string and arguments.
*/
- (void)logMessage:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) {
// gets message as string
va_list argp;
va_start(argp, format);
NSString *log = [[NSString alloc] initWithFormat:format arguments:argp];
va_end(argp);
// outputs to stdout
NSLog(@"%@", log);
// appends to output log
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"hh:mm:ss";
NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
_logTextView.text = [NSString stringWithFormat:@"%@%@%@: %@",
_logTextView.text,
([_logTextView.text length] > 0) ? @"\n" : @"",
dateString,
log];
}
@end