-
Notifications
You must be signed in to change notification settings - Fork 773
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
ID Token parsing and OpenID RP Certification #101
Conversation
100149e
to
9802336
Compare
Source/OIDAuthorizationService.m
Outdated
return; | ||
} | ||
|
||
NSURL *issuer = tokenResponse.request.configuration.discoveryDocument.issuer; |
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.
The issuer always needs to be validated. If not doing discovery then the issuer would need to be statically along with Authorization endpoint etc.
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.
Added the ability to manually specify issuer when not using discovery.
9802336
to
3c73785
Compare
Codecov Report
@@ Coverage Diff @@
## master #101 +/- ##
==========================================
+ Coverage 66.84% 73.32% +6.47%
==========================================
Files 54 57 +3
Lines 4217 4795 +578
==========================================
+ Hits 2819 3516 +697
+ Misses 1398 1279 -119
Continue to review full report at Codecov.
|
196d034
to
beb4b67
Compare
05f6853
to
c19f32e
Compare
c19f32e
to
5a3fc72
Compare
5569652
to
44e75a0
Compare
any update? |
313cd4a
to
21ddaa5
Compare
36b9235
to
cc96234
Compare
Updated the documentation & code comments for the ID Token features. Should be good to go now. Here is a good summary (taken from the code) of the validation that is performed. Basically we are following the OpenID Connect specification for verification of ID Tokens on the code flow, with the important note that we are exercising the option to rely only on the TLS validation of the direct connection between the token endpoint and client, and are not performing JWT signature verification (users of AppAuth are welcome to add this themselves, should they wish).
|
2f708cd
to
3f6f6d5
Compare
@WilliamDenniss - Need anything else for this? I have a project in flight this would be great for. |
Any progress on this? Do you need any help? |
@WilliamDenniss @StevenEWright What's the status of this pull request? |
@zboralski thank you for the ping. Ugh. Looks like I really dropped the ball on this. I’m sorry. I will look tonight. |
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.
General Question... where do we stand on Swift support? Have you had a chance to test the new APIs with Swift? Do they look OK?
Source/OIDAuthState.m
Outdated
@@ -125,7 +125,8 @@ @implementation OIDAuthState | |||
OIDTokenRequest *tokenExchangeRequest = | |||
[authorizationResponse tokenExchangeRequest]; | |||
[OIDAuthorizationService | |||
performTokenRequest:tokenExchangeRequest | |||
performTokenRequest:tokenExchangeRequest | |||
originalAuthorizationResponse:authorizationResponse | |||
callback:^(OIDTokenResponse *_Nullable tokenResponse, |
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.
How picky are we about colon alignment and style issues these days? I've seen a lot of people adopt clang-format, and I've been using it myself. It can be configured to mostly mirror our style guide. Nice to not have to worry about this stuff as much.
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.
You'll be pleased to know we're still picky. I took another stab at it, this one is kind of hard to fit in, PTAL. If you have any guidance please let me know.
@@ -46,6 +46,7 @@ extern NSString *const OIDOAuthorizationRequestCodeChallengeMethodS256; | |||
NSString *_scope; | |||
NSURL *_redirectURL; | |||
NSString *_state; | |||
NSString *_nonce; |
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.
Looks like this is supposed to be a nullable field. Shouldn't it be:
NSString *_Nullable _nonce;
?
But I realize we don't have nullability attributes on the other property-backing ivars either. Am surprised this hasn't raised warnings / etc. Obviously I am not suggesting we deal with or "fix" this issue here. Mostly a question more so than a comment.
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.
Hmm, at least the property itself is declared as nullable. I hope to remove this code in the near future, once 32bit is gone.
Source/OIDAuthorizationService.m
Outdated
NSTimeInterval expiresAtDifference = [idToken.expiresAt timeIntervalSinceNow]; | ||
if (expiresAtDifference < 0) { | ||
NSError *invalidIDToken = | ||
[OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError |
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.
+4
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.
Done
|
||
// OpenID Connect Core Section 3.1.3.7. rule #10 | ||
// Validates that the issued at time is not more than +/- 5 minutes on the current time. | ||
NSTimeInterval issuedAtDifference = [idToken.issuedAt timeIntervalSinceNow]; |
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.
Just curious - I know we have other date/time related validations which take the local clock into account, but this seems like it may be the most specific. Is that true? I don't know why, on an iOS device, someone would have clock skew on the order of minutes - especially considering iPhones in particular should have fairly constant network connectivity, and should be getting their clocks updated regularly - but hypothetically if someone's clock were off by a couple minutes, would you expect this to have a chance of failing for that reason where-as it wouldn't before? (Reiterating it's just a question for my own edification, not that I see it as a problem/issue.)
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 too many people set the time manually, I agree – but perhaps some do. I'm more concerned about server clock skew.
I don't really know what const to use here. The docs say we should "reject tokens that were issued too far away from the current time", but don't specify what "too far away" is, so I plucked 5 minutes out of the air, since it seemed short, but also long enough to account for multiple-minutes of clock skew on both sides.
@remarks iat | ||
@see http://openid.net/specs/openid-connect-core-1_0.html#IDToken | ||
*/ | ||
@property(nonatomic, readonly) NSDate *issuedAt; |
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.
There seem to exist cases in which this field is optional? Should it be nullable here?
When a max_age request is made or when auth_time is requested as an Essential Claim, then this Claim is REQUIRED; otherwise, its inclusion is OPTIONAL.
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.
Appears to be required to me:
iat
REQUIRED. Time at which the JWT was issued. Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time.
I think you're quoting from auth_time
?
Source/OIDIDToken.m
Outdated
if ([value isKindOfClass:[NSArray class]]) { | ||
return value; | ||
} | ||
return @[value]; |
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.
In this case it's expected value is a type of string, right? You've been so diligent about checking types... do we want to check that as well?
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.
Done.
Source/OIDIDToken.m
Outdated
return value; | ||
} | ||
NSNumber *valueAsNumber = (NSNumber *)value; | ||
return [NSDate dateWithTimeIntervalSince1970:[valueAsNumber longLongValue]]; |
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.
dot notation for properties? (longLongValue below as well)
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.
Done.
Source/OIDServiceConfiguration.h
Outdated
@@ -48,6 +49,10 @@ typedef void (^OIDServiceConfigurationCreated) | |||
*/ | |||
@property(nonatomic, readonly) NSURL *tokenEndpoint; | |||
|
|||
/*! @brief The token exchange and refresh endpoint URI. | |||
*/ | |||
@property(nonatomic, readonly) NSURL *issuer; |
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.
Should this be nullable?
@property(nonatomic, readonly, nullable) NSURL *issuer;
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.
Yes. Done.
@param tokenEndpoint The token exchange and refresh endpoint URI. | ||
@param issuer The OpenID Connect issuer. | ||
*/ | ||
- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint |
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.
Should we mark init
as unavailable in the header?
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 think we do already? - (instancetype)init NS_UNAVAILABLE;
Source/OIDServiceConfiguration.m
Outdated
@synthesize registrationEndpoint = _registrationEndpoint; | ||
@synthesize discoveryDocument = _discoveryDocument; | ||
|
||
- (instancetype)init | ||
OID_UNAVAILABLE_USE_INITIALIZER(@selector( | ||
initWithAuthorizationEndpoint: | ||
tokenEndpoint: | ||
registrationEndpoint:) | ||
tokenEndpoint:) | ||
); | ||
|
||
- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint |
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.
Looks like this is the actual designated initializer? Should we mark it as such in the header?
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.
So this method is not actually exposed in the header at all.
We're expecting people to either specify the endpoints manually, OR use a discovery doc – but not both. Do you think we should revise this?
Source/OIDAuthorizationService.m
Outdated
// OpenID Connect Core Section 3.1.3.7. rule #11 | ||
// Validates the nonce. | ||
NSString *nonce = authorizationResponse.request.nonce; | ||
if (nonce && ![idToken.nonce isEqual:nonce]) { |
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 if you are already aware, but there is a problem here. A nonce isn't sent with the call and then it expects one here.
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 the authorization request doesn't set nonce, then this check will be skipped (if (nonce &&
… should achieve that). Are you sure there's a problem?
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 have a project using this library with an IdentityServer4 server. The problem is that authorizationResponse.request.nonce
is a valid nonce and idToken.nonce
is null.
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.
OpenID Connect Core Section 2 states:
If present in the Authentication Request, Authorization Servers MUST include a nonce Claim in the ID Token with the Claim Value being the nonce value sent in the Authentication Request.
I believe we are implementing this correctly in AppAuth , is there a bug in IdentityServer4?
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 have opened a bug with IdentityServer4 here: IdentityServer/IdentityServer4#2180
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.
@WilliamDenniss
Section 12.1
Does not state anything about needing a nonce to verify a Refresh Request. Not sure AppAuth is really doing the right thing here.
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.
Ah, I didn't realize you were referring the refresh token flow.
Section 12.1 is a bit ambiguous for nonce, but yeah I don't think the intention was for nonce to be required in the ID Token for the token response during refreshes. I sent a note to the Connect mailing list regarding the possible ambiguity. Google doesn't return the nonce in refreshed ID Tokens either.
I've fixed this in the PR. Thanks very much for engaging and reporting this problem!
463b66a
to
f445320
Compare
LGTM |
Our identify server requires mobile clients to send nonce. I verified that this branch will work for our case. Are we merging this PR in the near future? Thanks! |
e0b3a12
to
c1db1d6
Compare
– New OIDIDToken object to parse ID Tokens. – Token exchange now validates the ID Token pursuant to OpenID Connect Core Section 3.1.3.7. (see code comments for details).
c1db1d6
to
e30ea9d
Compare
ID Token Parsing
OpenID RP Certification Tests
Response Type and Response Mode
scope Request Parameter
nonce Request Parameter
Client Authentication
ID Token
*AppAuth for iOS & macOS only supports
alg:none
validation.UserInfo Endpoint
Fixes:
#4 Support "nonce" OpenID Connect auth request parameter.
#17 Support JWT decoding and validation (conditional: alg:none based validation only).
#102 OpenID Connect RP Conformance Testing.