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

Extend Token with HasClaims for custom claims #204

Merged
merged 4 commits into from
Aug 12, 2024

Conversation

mikenachbaur-okta
Copy link
Contributor

@mikenachbaur-okta mikenachbaur-okta commented Aug 2, 2024

The IETF RFCs for Tokens not only include a couple extra properties that were not previously addressed, but it states that the responses may be extended with custom information. Authorization servers, such as Keycloak, includes metadata in the token response indicating refresh token expiration, or other data.

Since Token responses can be extended, it stands to reason that these objects should conform to HasClaims just like other models, to allow for them to be adapted to different authentication scenarios.

This update further extends the support for Claims to resolve edge-cases in how convertible values were handled, unifying support around mapping claims to scalar values, arrays, or dictionaries, and cleaning up how JSON objects are mapped.

Finally, since the result of refresh operations often doesn't include data provided in the initial token exchange request (such as device_secret), the process for merging tokens during refresh operations has been moved to a protocol. This is not yet public, but this may be exposed in the future.

This PR:

  1. Alters Token to conform to HasClaims so its raw JSON token response data is stored and persisted
  2. Defines properties defined by the OAuth2 specs, including required values (such as Token Type, Access Token, etc)
  3. Includes version migration support to handle loading and parsing of tokens stored with previous versions of the SDK
  4. Refines and makes the HasClaims value functions work consistently (since there were gaps before)
  5. Cleans up JSON handling, as well as documentation updates

The IETF RFCs for Tokens not only include a couple extra properties that were not previously addressed, but it states that the responses may be extended with custom information. Authorization servers, such as Keycloak, includes metadata in the token response indicating refresh token expiration, or other data.

Since Token responses can be extended, it stands to reason that these objects should conform to `HasClaims` just like other models, to allow for them to be adapted to different authentication scenarios.

This update further extends the support for Claims to resolve edge-cases in how convertible values were handled, unifying support around mapping claims to scalar values, arrays, or dictionaries, and cleaning up how JSON objects are mapped.

Finally, since the result of refresh operations often doesn't include data provided in the initial token exchange request (such as `device_secret`), the process for merging tokens during refresh operations has been moved to a protocol. This is not yet public, but this may be exposed in the future.
Copy link

@stevelind-okta stevelind-okta left a comment

Choose a reason for hiding this comment

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

Cool refactor, nice docs as usual!

I am having trouble following what happened to nested Array and Dictionary types. Is there some way they are being recursively handled as ClaimConvertible types?

Comment on lines -20 to -21
extension Array<String>: ClaimConvertable {}
extension Dictionary<String, String>: ClaimConvertable {}

Choose a reason for hiding this comment

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

Does JWTClaim take care of array and dictionary?

Copy link
Contributor Author

@mikenachbaur-okta mikenachbaur-okta Aug 9, 2024

Choose a reason for hiding this comment

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

It's unnecessary since the accessors for working with claims (e.g. value(...) or subscript methods) address arrays or dictionaries already.

The goal for ClaimConvertable is to map some JSON primitive supplied from the server into a more developer-friendly value (e.g. from a string to URL, or to an enum, etc.). As a result, arrays and dictionaries themselves aren't all that interesting to be converted.

In fact, the only reason Array and Dictionary were made to be convertable in the first place was because the value functions were being used inconsistently, which prevented their values from being properly converted in the first place.

@@ -12,25 +12,42 @@

import Foundation

@_documentation(visibility: private)

Choose a reason for hiding this comment

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

curious, so this hides the extension from generated docs or from Xcode as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From the generated docs. Many of these just clutter up the docs. For reference, here's the list of underscored attributes.

clientId: "clientid",
scopes: "openid"),
clientSettings: [ "client_id": "foo" ]))
let token = try! Token(id: "TokenId",

Choose a reason for hiding this comment

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

Suggested change
let token = try! Token(id: "TokenId",
guard let token = try? Token(id: "TokenId", ... else { XCTFail() }?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's in a class property, not a function block, so I opted to simply force-unwrapping since it's in a unit test. Though I could move initializing the Token to the setUpWithError function instead.

clientId: "clientid",
scopes: scopes),
clientSettings: [ "client_id": "clientid" ]))
return try! Token(id: "TokenId",

Choose a reason for hiding this comment

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

Do we want to test anything about these tokens once created? Or is the constructor enough of a test by itself?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's the Tests/AuthFoundationTests/TokenTests.swift class that exercises the various ways the token can be initialized, including migrating from V1 data.

This mock token utility is simply there to return tokens for use in other tests.

Force unwrapping makes me feel icky, but as a utility class, if I required all the other tests using this to try, it would make the tests messier.

@mikenachbaur-okta mikenachbaur-okta merged commit c41191a into master Aug 12, 2024
8 checks passed
@mikenachbaur-okta mikenachbaur-okta deleted the OKTA-750342-TokenClaim branch August 12, 2024 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants