-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MOB-415: Add access to access token and save it in the keychain #11
Conversation
|
||
/// API Constants (Production) | ||
#define IDME_WEB_VERIFY_GET_AUTH_URI @"https://api.id.me/oauth/authorize?client_id=%@&redirect_uri=%@&response_type=token&scope=%@" | ||
#define IDME_WEB_VERIFY_GET_USER_PROFILE @"https://api.id.me/api/public/v2/%@.json?access_token=%@" | ||
|
||
/// Data Constants | ||
#define IDME_WEB_VERIFY_ACCESS_TOKEN_PARAM @"access_token" | ||
#define IDME_WEB_VERIFY_EXPIRATION_PARAM @"expires_in" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please keep expires_in
at the same column of access_token
[self clearWebViewCacheAndCookies]; | ||
} | ||
|
||
return self; | ||
} | ||
|
||
+ (void)initializeWithClientID:(NSString *)clientID redirectURI:(NSString *)redirectURI { | ||
[[IDmeWebVerify sharedInstance] setClientID:clientID]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want to support changing the clientID after the initialize method was called? I think these properties should be readonly and here we should call a private constructor which pass the id and url
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently sharedInstance
is initializing the instance. Should we require calling initialize...
before calling sharedInstance
and then calling the init from the initialize
?
@param forceRefreshing Force the SDK to refresh the token and do not use the current one. | ||
@param callback A block that returns an NSString object representing a valid access token or an NSError object. | ||
*/ | ||
- (void)getAccessTokenWithScope:(NSString*)scope forceRefreshing:(BOOL)force result:(IDmeVerifyWebVerifyResults _Nonnull)callback; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add nullability indicator to scope
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; | ||
NSUInteger statusCode = [httpResponse statusCode]; | ||
if ([data length]) { | ||
NSDictionary *results = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pass an error variable and check if everything was ok
[self.keychainData clean]; | ||
} | ||
|
||
- (void)getAccessTokenWithScope:(NSString*)scope forceRefreshing:(BOOL)force result:(IDmeVerifyWebVerifyResults)callback{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nullability indicators are missing
@@ -343,4 +384,21 @@ - (NSString * _Nullable)clientID{ | |||
|
|||
return (_clientID) ? _clientID : nil; | |||
} | |||
|
|||
#pragma mark - Helpers | |||
- (void)setExpirationDateWith:(NSNumber*)seconds{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add nullability indicator
self.keychainData.expirationDate = [NSDate dateWithTimeIntervalSinceNow:[seconds doubleValue]]; | ||
} | ||
|
||
- (UIViewController*) topMostController |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add nullability indicator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, I'm not sure if we should use this here, it is related to the getAccessToken feature
return; | ||
} else if (![scope isEqualToString:self.keychainData.scope]) { | ||
[self logout]; | ||
[self verifyUserInViewController:[self topMostController] scope:scope withTokenResult:callback]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should show the login form automatically. This function getAccessToken
is intended to just get a fresh token to make an API call, for example. Remember that this could be done in a background thread where the developer maybe don't want or can't show some UI. In this case I would call the callback with an error. This mostly must be defined in the issue discussion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This depends on how scope change is handled. I would think that scope change would require a new login and should therefore not be called from a background thread. But yes it should be discussed in the issue.
|
||
NSError* error; | ||
[SAMKeychain setPasswordData:dictionaryRep forService:[NSBundle mainBundle].bundleIdentifier account:IDME_KEYCHAIN_DATA_ACCOUNT error:&error]; | ||
NSLog(@"%@", error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if persist
fails, shouldn't fail the entire sign in process?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure. Keychain might fail but the sign in succeeded. So the user will be able to use the app while he does not shut it down. He would have to sign in again after shutting down the app as the token has not been saved to the keychain. But it is better than make the sign in fail when it succeeded in reality.
-(void)loadFromKeychain{ | ||
NSString *error; | ||
NSData* data = [SAMKeychain passwordDataForService:[NSBundle mainBundle].bundleIdentifier account:IDME_KEYCHAIN_DATA_ACCOUNT]; | ||
NSDictionary *dictionary = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should send back this error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is internal and just called in the init
@param clientID The clientID provided by ID.me when registering the app at @b http://developer.id.me | ||
@param redierectURI The redirectURI provided to ID.me when registering your app at @b http://developer.id.me | ||
@param affiliationType The type of group verficiation that should be presented. Check the @c IDmeVerifyAffiliationType typedef for more details | ||
@param scope The type of group verficiation that should be presented. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo in verification
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copied it from the previous method 😱
PR yet depending on scope handling discussion |
NSDate* expiration = self.keychainData.expirationDate; | ||
NSDate* now = [[NSDate alloc] init]; | ||
if ([now compare:expiration] != NSOrderedAscending) { | ||
// TODO: refresh token |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mats-claassen Are you planning on adding refreshing tokens in this branch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. I want to implement that in a separate branch linking to #7
404ef04
to
f10b818
Compare
f10b818
to
07d2100
Compare
@param webVerificationResults A block that returns an NSDictionary object and an NSError object. The verified user's profile is stored in an @c NSDictionary object as @c JSON data. If no data was returned, or an error occured, @c NSDictionary is @c nil and @c NSError returns an error code and localized description of the specific error that occured. | ||
*/ | ||
- (void)getUserProfileWithResult:(IDmeVerifyWebVerifyResults _Nonnull)webVerificationResults; | ||
- (void)getUserProfileWithScope:(NSString* _Nullable)scope andResult:(IDmeVerifyWebVerifyResults _Nonnull)webVerificationResults; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t use “and” to link keywords that are attributes of the receiver. (https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingMethods.html#//apple_ref/doc/uid/20001282-BCIGIJJF)
NSUInteger statusCode = [httpResponse statusCode]; | ||
if ([data length]) { | ||
NSError* error; | ||
NSDictionary *results = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is only needed if statusCode == 200
, right? maybe you can move it to that if
.
I'm not sure about the structure of this if else
block. What would happen if statusCode == 401
and the response doesn't have a body? Currently it is returning the error IDmeWebVerifyErrorCodeVerificationDidFailToFetchUserProfile
instead of doing the refresh process.
What do you think?
|
||
if (!latestScope) { | ||
// no token has been requested yet | ||
callback(nil, nil, nil); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't an error be returned in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This happens when there has never been a login. We could return an error IDmeWebVerifyErrorCodeNoSuchScope
-(NSDate* _Nullable)expirationDateForScope:(NSString* _Nonnull)scope; | ||
-(NSString* _Nullable)refreshTokenForScope:(NSString* _Nonnull)scope; | ||
|
||
-(void)setToken:(NSString * _Nonnull)accessToken expirationDate:(NSDate * _Nonnull)date |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please move each parameter to a new line to improve readability
options:NSPropertyListMutableContainersAndLeaves | ||
format:nil | ||
error:&error]; | ||
self.tokensByScope = [NSMutableDictionary dictionaryWithDictionary:dictionary]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't you set latestScope
at this moment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right
-(NSString* _Nullable)refreshTokenForScope:(NSString* _Nonnull)scope; | ||
|
||
-(void)setToken:(NSString * _Nonnull)accessToken expirationDate:(NSDate * _Nonnull)date | ||
refreshToken:(NSString * _Nullable)refreshToken forScope:(NSString * _Nonnull)scope; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refresh token must not be nullable, right? maybe add a todo to change it when we are implementing refresh feature
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is nullable as it will not be updated when the access token gets updated so passing nil will leave the old refresh token. We can change this if it is not clear. Or we should document it
@param webVerificationResults A block that returns an NSDictionary object and an NSError object. The verified user's profile is stored in an @c NSDictionary object as @c JSON data. If no data was returned, or an error occured, @c NSDictionary is @c nil and @c NSError returns an error code and localized description of the specific error that occured. | ||
*/ | ||
- (void)getUserProfileWithResult:(IDmeVerifyWebVerifyResults _Nonnull)webVerificationResults; | ||
- (void)getUserProfileWithScope:(NSString* _Nullable)scope andResult:(IDmeVerifyWebVerifyResults _Nonnull)webVerificationResults; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't use the same block type for getting the token and user profile due to them should have different argument list. When requesting the access token doesn't matter the profile parameter, and when requesting the user profile probably you don't want the access token.
What do you think? Maybe this is a change for a new PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I did just keep the block as it was in the previous version.
|
||
if (force) { | ||
// TODO: refresh token | ||
callback(nil, nil, nil); | ||
callback(nil, nil); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
due to we won't support refresh token feature for a time, what do you think about sending back a not-implemented-like error
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should remove the force
for now.
@@ -204,16 +192,13 @@ - (void)getAccessTokenWithScope:(NSString* _Nullable)scope forceRefreshing:(BOOL | |||
NSDate* now = [[NSDate alloc] init]; | |||
if ([now compare:expiration] != NSOrderedAscending) { | |||
// TODO: refresh token | |||
callback(nil, nil, nil); | |||
callback(nil, nil); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and here return an unauthorized error?
@@ -119,12 +119,12 @@ - (void)verifyAction:(id)sender { | |||
} | |||
|
|||
- (void)tokenTestAction:(id)sender { | |||
[[IDmeWebVerify sharedInstance] getUserProfileWithResult:^(NSDictionary *userProfile, NSError *error, NSString *accessToken) { | |||
[[IDmeWebVerify sharedInstance] getUserProfileWithScope: @"wallet" result:^(NSDictionary *userProfile, NSError *error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't we use some test scope? do we want wallet to be public?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry this should not be there
/// Keychain Constants | ||
#define IDME_KEYCHAIN_DATA_ACCOUNT @"IDME_KEYCHAIN_DATA" | ||
#define IDME_EXPIRATION_DATE @"IDME_EXPIRATION_DATE" | ||
#define IDME_REFRESH_TOKEN @"IDME_REFRESH_TOKEN" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mats-claassen We should drop refresh token for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything is implemented so that a refresh token can be easily supported in the future. Do you want us to remove everything related to refresh tokens?
// IDmeWebVerifyKeychainData.m | ||
// WebVerifySample | ||
// | ||
// Created by Mathias Claassen on 12/9/16. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mats-claassen We should be able to drop the "created by" since we're using git :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
MOB-415: Add access to access token and save it in the keychain Fixes #6 * Handle several scopes and their associated access tokens * Separate the callback block for profile and token results. * Throw appropriate errors when there is no valid token
Fixes #6
Adds access token management code according to IDme/ID.me-WebVerify-SDK-Android#9
Also adds SAMKeychain dependency for keychain access. Dependency is added as a pod dependency.