Skip to content

[ CDM-243 ] [ CDM-245 ] Orcid Provider MFA Support & Token response mfaAuthenticated key #471

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

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from

Conversation

dauglyon
Copy link
Collaborator

@dauglyon dauglyon commented Jul 15, 2025

Adds MFA boolean to RemoteIdentity and StoredToken classes, which get stored in Mongo. Adds JWT parsing to extract the AMR claim from the Orcid login token during login. Modifies the token to include MFA status.

A nice summary of all the changes from claude:

Key Changes

Core Implementation:

  • Added mfa field to StoredToken (stored as mfa in MongoDB)
  • Extended RemoteIdentityDetails to capture MFA status from identity providers
  • Updated APIToken to expose MFA status via /api/V2/token endpoint

ORCID Provider Enhancement:

  • Changed scope from /authenticate to openid to receive JWT ID tokens
  • Parse Authentication Method Reference (AMR) claim to detect MFA usage
  • Set mfa=USED when AMR contains "mfa"

Authentication Flow:

  • OAuth login: MFA status determined from identity provider and stored on LOGIN token
  • Password login: MFA status set to uknown (not applicable)
  • Agent/Dev/Service tokens: MFA status set to uknown (no authentication context)

API Response

The /api/V2/token endpoint now returns mfa with tri-state semantics:

  • USED: User authenticated with MFA during token creation (e.g. Orcid with MFA)
  • NOT_USED: User explicitly chose not to use MFA when available (e.g. Orcid without MFA)
  • UNKNOWN: MFA status unknown or not applicable to authentication method (e.g. Google (mfa inspection not supported), or a dev token)

Database Schema

  • Tokens collection: Added mfa field
  • Users collection: Added mfa field to identity objects


@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((email == null) ? 0 : email.hashCode());
result = prime * result + ((fullname == null) ? 0 : fullname.hashCode());
result = prime * result + ((mfaAuthenticated == null) ? 0 : mfaAuthenticated.hashCode());
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not sure this is right, as it is a boolean.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

(though it seems to be working)

@dauglyon dauglyon assigned dauglyon and unassigned dauglyon Jul 15, 2025
@dauglyon

This comment has been minimized.

dauglyon added 4 commits July 15, 2025 12:05
Update tests to handle MFA field additions and ORCID OpenID Connect changes:
- Fix hash codes in RemoteIdentityTest for new MFA field
- Update ORCID provider tests for openid scope instead of /authenticate
- Add mfaAuthenticated field to API response expectations
- Fix non-ORCID provider test to use null MFA status
- Update identity ordering in user endpoint tests
Copy link

codecov bot commented Jul 16, 2025

Codecov Report

Attention: Patch coverage is 97.53086% with 2 lines in your changes missing coverage. Please review.

Project coverage is 93.40%. Comparing base (47f6a01) to head (3d9e93e).

Additional details and impacted files
@@              Coverage Diff              @@
##             develop     #471      +/-   ##
=============================================
+ Coverage      93.37%   93.40%   +0.03%     
- Complexity      2151     2168      +17     
=============================================
  Files            126      126              
  Lines           7558     7623      +65     
  Branches        1184     1202      +18     
=============================================
+ Hits            7057     7120      +63     
  Misses           458      458              
- Partials          43       45       +2     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dauglyon dauglyon marked this pull request as ready for review July 17, 2025 17:17
@dauglyon dauglyon changed the title MFA Support Orcid Provider MFA Support & Token response mfaAuthenticated key Jul 17, 2025
@dauglyon dauglyon changed the title Orcid Provider MFA Support & Token response mfaAuthenticated key [ CDM-243 ] [ CDM-245 ] Orcid Provider MFA Support & Token response mfaAuthenticated key Jul 17, 2025
dauglyon added 2 commits July 17, 2025 12:31
  Move MFA status from being computed from user identities to being stored
  as a boolean field on tokens themselves. This provides per-token MFA
  tracking and eliminates the need for complex identity lookups.
@dauglyon dauglyon force-pushed the MFAStatus branch 2 times, most recently from 52f871c to 3d9e93e Compare July 17, 2025 23:25
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Seems to be some stochasticity in these tests, as I had to change the order. I haven't identified a change I made that would cause this directly. Seen in src/test/resources/us/kbase/test/auth2/service/ui/MeTest_getMeMaximalInput.testdata, src/test/java/us/kbase/test/auth2/service/ui/MeTest.java, src/test/java/us/kbase/test/auth2/service/api/UserEndpointTest.java

Copy link
Member

@MrCreosote MrCreosote left a comment

Choose a reason for hiding this comment

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

I haven't looked at tests yet as this review is already pretty huge

*/
private Boolean parseAmrClaim(final String idToken) {
if (idToken == null || idToken.trim().isEmpty()) {
return null;
Copy link
Member

Choose a reason for hiding this comment

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

If the token is expected this should throw an error

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was throwing in tests, but I think fixed those tests, so I bet I can remove this check now

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm going to keep this check so we don't get runtime errors if ORCID fails to return a JWT, but I am going to log that as happening. The user will see their MFA status as unknown, but the flow won't fail. If the JWT is malformed, however, I am going to throw.

Copy link
Member

Choose a reason for hiding this comment

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

Unless there's a documented reason oid wouldn't return a token when we've asked for one, this should fail IMO. Don't code for circumstances that can't happen, just fail

Copy link
Collaborator Author

@dauglyon dauglyon Jul 18, 2025

Choose a reason for hiding this comment

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

The circumstance would be "they change our oAuth scope access" (eg decide to remove our member API access) which can happen without any change on our end. Trying to be a bit defensive. Seems we shouldn't prevent all orcid logins in that case. But if we're ok with that, I can throw here too

ALSO if we throw here, all auth2 deploys will REQUIRE orcid member api access

Copy link
Member

Choose a reason for hiding this comment

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

Ok. How about a 4th enum value for this case to distinguish it from the provider doesn't support it at all?

Copy link
Member

Choose a reason for hiding this comment

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

Actually - does changing the scope mean that other auth installs will fail if they don't have member api access?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm going to actually have to double check the api on this; it looks like the docs say conflicting things (you need member api to see the jwt claims, but that the openid scope is available on the public api and it's only purpose is to return the jwt) or it's possible it will return two completely different jwts depending on scope or api access... anyway I will check

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.

2 participants