Skip to content
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

Merged
merged 3 commits into from
Jun 2, 2018

Conversation

WilliamDenniss
Copy link
Member

@WilliamDenniss WilliamDenniss commented Mar 25, 2017

ID Token Parsing

  • The 5 required fields are exposed as params.
  • Authorization code exchange exchange now validates the ID Token iss, aud, and iat claims.

OpenID RP Certification Tests

Response Type and Response Mode

  • rp-response_type-code

scope Request Parameter

  • rp-scope-userinfo-claims

nonce Request Parameter

  • rp-nonce-invalid

Client Authentication

  • rp-token_endpoint-client_secret_basic

ID Token

  • rp-id_token-aud
  • rp-id_token-kid-absent-single-jwks*
  • rp-id_token-sig-none
  • rp-id_token-issuer-mismatch
  • rp-id_token-kid-absent-multiple-jwks*
  • rp-id_token-bad-sig-rs256*
  • rp-id_token-iat
  • rp-id_token-sig-rs256*
  • rp-id_token-sub

*AppAuth for iOS & macOS only supports alg:none validation.

UserInfo Endpoint

  • rp-userinfo-bad-sub-claim
  • rp-userinfo-bearer-header

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.

@WilliamDenniss WilliamDenniss force-pushed the certification branch 2 times, most recently from 100149e to 9802336 Compare March 25, 2017 21:40
return;
}

NSURL *issuer = tokenResponse.request.configuration.discoveryDocument.issuer;
Copy link
Collaborator

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.

Copy link
Member Author

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.

@codecov-io
Copy link

codecov-io commented Mar 25, 2017

Codecov Report

Merging #101 into master will increase coverage by 6.47%.
The diff coverage is 89.34%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
Source/OIDAuthorizationService.h 0% <ø> (ø) ⬆️
Source/OIDTokenResponse.h 100% <ø> (ø) ⬆️
Source/OIDAuthState.m 46.99% <0%> (+3.73%) ⬆️
Source/OIDAuthorizationRequest.h 100% <100%> (ø) ⬆️
Source/OIDServiceConfiguration.h 100% <100%> (+25%) ⬆️
UnitTests/OIDAuthorizationRequestTests.m 100% <100%> (ø) ⬆️
Source/OIDAuthorizationRequest.m 86.04% <33.33%> (-7.34%) ⬇️
Source/OIDServiceConfiguration.m 78.4% <72.72%> (-14.02%) ⬇️
Source/OIDIDToken.h 75% <75%> (ø)
Source/OIDAuthorizationService.m 62.22% <80.39%> (+45.18%) ⬆️
... and 14 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c16ebbe...e30ea9d. Read the comment docs.

@WilliamDenniss WilliamDenniss force-pushed the certification branch 2 times, most recently from 196d034 to beb4b67 Compare March 26, 2017 15:32
@WilliamDenniss WilliamDenniss changed the title ID Token parsing. ID Token parsing and OpenID RP Certification Apr 30, 2017
@WilliamDenniss WilliamDenniss force-pushed the certification branch 2 times, most recently from 5569652 to 44e75a0 Compare May 21, 2017 23:45
@zboralski
Copy link

any update?

@WilliamDenniss WilliamDenniss force-pushed the certification branch 2 times, most recently from 313cd4a to 21ddaa5 Compare October 12, 2017 21:58
@WilliamDenniss WilliamDenniss force-pushed the certification branch 6 times, most recently from 36b9235 to cc96234 Compare January 5, 2018 20:28
@WilliamDenniss
Copy link
Member Author

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).

// If an ID Token is included in the response, validates the ID Token following the rules
// in OpenID Connect Core Section 3.1.3.7 for features that AppAuth directly supports
// (which excludes rules #1, #4, #5, #7, #8, #12, and #13). Regarding rule #6, ID Tokens
// received by this class are received via direct communication between the Client and the Token
// Endpoint, thus we are exercising the option to rely only on the TLS validation. AppAuth
// has a zero dependencies policy, and verifying the JWT signature would add a dependency.
// Users of the library are welcome to perform the JWT signature verification themselves should
// they wish.

@jakeholland
Copy link

@WilliamDenniss - Need anything else for this? I have a project in flight this would be great for.

@zboralski
Copy link

Any progress on this? Do you need any help?

@zboralski
Copy link

@WilliamDenniss @StevenEWright What's the status of this pull request?

@StevenEWright
Copy link
Collaborator

@zboralski thank you for the ping. Ugh. Looks like I really dropped the ball on this. I’m sorry. I will look tonight.

Copy link
Collaborator

@StevenEWright StevenEWright left a 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?

@@ -125,7 +125,8 @@ @implementation OIDAuthState
OIDTokenRequest *tokenExchangeRequest =
[authorizationResponse tokenExchangeRequest];
[OIDAuthorizationService
performTokenRequest:tokenExchangeRequest
performTokenRequest:tokenExchangeRequest
originalAuthorizationResponse:authorizationResponse
callback:^(OIDTokenResponse *_Nullable tokenResponse,
Copy link
Collaborator

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.

Copy link
Member Author

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;
Copy link
Collaborator

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.

Copy link
Member Author

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.

NSTimeInterval expiresAtDifference = [idToken.expiresAt timeIntervalSinceNow];
if (expiresAtDifference < 0) {
NSError *invalidIDToken =
[OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+4

Copy link
Member Author

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];
Copy link
Collaborator

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.)

Copy link
Member Author

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;
Copy link
Collaborator

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.

Copy link
Member Author

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?

if ([value isKindOfClass:[NSArray class]]) {
return value;
}
return @[value];
Copy link
Collaborator

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return value;
}
NSNumber *valueAsNumber = (NSNumber *)value;
return [NSDate dateWithTimeIntervalSince1970:[valueAsNumber longLongValue]];
Copy link
Collaborator

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)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -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;
Copy link
Collaborator

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;

Copy link
Member Author

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
Copy link
Collaborator

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?

Copy link
Member Author

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;

@synthesize registrationEndpoint = _registrationEndpoint;
@synthesize discoveryDocument = _discoveryDocument;

- (instancetype)init
OID_UNAVAILABLE_USE_INITIALIZER(@selector(
initWithAuthorizationEndpoint:
tokenEndpoint:
registrationEndpoint:)
tokenEndpoint:)
);

- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint
Copy link
Collaborator

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?

Copy link
Member Author

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?

// OpenID Connect Core Section 3.1.3.7. rule #11
// Validates the nonce.
NSString *nonce = authorizationResponse.request.nonce;
if (nonce && ![idToken.nonce isEqual:nonce]) {
Copy link

@ghost ghost Mar 27, 2018

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.

Copy link
Member Author

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?

Copy link

@ghost ghost Mar 27, 2018

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.

Copy link
Member Author

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?

Copy link

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

Copy link

@ghost ghost Apr 2, 2018

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.

Copy link
Member Author

@WilliamDenniss WilliamDenniss Apr 20, 2018

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!

@WilliamDenniss WilliamDenniss force-pushed the certification branch 3 times, most recently from 463b66a to f445320 Compare April 20, 2018 23:14
@StevenEWright
Copy link
Collaborator

LGTM

@ShaneQi
Copy link

ShaneQi commented May 23, 2018

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!

@WilliamDenniss WilliamDenniss force-pushed the certification branch 2 times, most recently from e0b3a12 to c1db1d6 Compare June 1, 2018 21:00
– 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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants