Skip to content

BREAKING: implement Native OIDC as per MSC 3861 #2024

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 23 commits into
base: main
Choose a base branch
from

Conversation

TheOneWithTheBraid
Copy link
Contributor

@TheOneWithTheBraid TheOneWithTheBraid commented Feb 7, 2025

BREAKING: changes to the database API to better reflect the OIDC state

This implementation is based on the draft of native OIDC I implemented in < polycule >. It can be found here. Unlike in my initial draft, this implementation of course does not rely on any Flutter package but ships a minimal OIDC state machine relying on the matrix client to provide the native callback result via a Completer.

This implementation does not aim to comply to the full OIDC standard but only the subset required as per the MSCs stated below.

This PR does not aim to implement OIDC device flow as per MSC 4108 nor the changes for the application services as per MSC 4190.

Roadmap:

Implements:

  • MSC 3861 - Next-generation auth for Matrix, based on OAuth 2.0/OIDC
    • MSC 1597 - Better spec for matrix identifiers
    • MSC 2964 - Usage of OAuth 2.0 authorization code grant and refresh token grant
    • MSC 2965 - OAuth 2.0 Authorization Server Metadata discovery
    • MSC 2966 - Usage of OAuth 2.0 Dynamic Client Registration in Matrix
    • MSC 2967 - API scopes
    • MSC 3824 - OIDC aware clients
    • MSC 4108 - Mechanism to allow OIDC sign in and E2EE set up via QR code - not planned for this PR
    • MSC 4190 - Device management for application services - not planned for this PR
    • MSC 4191 - Account management deep-linking

@TheOneWithTheBraid
Copy link
Contributor Author

A comment on the changes to the database API:

I initially intended to just keep all OIDC-related state (device ID, OAuth2.0 client ID, auth metadata) in memory until the OIDC flow completes. I sadly figured out the Dart process might get killed by the platform for battery optimization. In order to complete the flow after the main process was killed, I decided to store the OAuth2 client ID and the homeserver's auth metadata in the database as well as to split the device ID from the remaining client store since now the device ID is present a long time before the rest of the account is available.

Even though I so far already implemented the database API changes with this in mind, there is no way to resume an OIDC flow from a new thread yet.

@TheOneWithTheBraid
Copy link
Contributor Author

TheOneWithTheBraid commented Feb 7, 2025

Outdated review hint

Review notice: So far, the default behavior of the updated Client.getWellknown is to only check the brand new /auth_metadata endpoint. This might require Synapse nightly builds to work. Client developers however are free to use the previous revision of the MSC suggesting to use the /auth_issuer endpoint or the .well-known entry containing the org.matrix.msc2965.authentication field. All three possible implementations are supported in this PR. Since the default is definitely going to be the new /auth_metadata, the implementation already suggests this at this point.

EDIT: I adjusted the behavior of Client.getWellknown to cope with all three permitted approaches of OIDC discovery. The implementation will therefore work with both stable Synapse releases as well as cutting edge Synapse builds already using the newest approach.

You can now easily use e.g. synapse-oidc.element.dev for testing.

@TheOneWithTheBraid TheOneWithTheBraid marked this pull request as ready for review February 9, 2025 11:56
@TheOneWithTheBraid TheOneWithTheBraid requested a review from a team as a code owner February 9, 2025 11:56
@TheOneWithTheBraid
Copy link
Contributor Author

The < polycule > side implementation is now merged and works like a charm. Maybe that's helpful for testing this PR in real world: https://gitlab.com/polycule_client/polycule/-/merge_requests/245

@TheOneWithTheBraid
Copy link
Contributor Author

Since I unaskedly opened this PR, I'd kindly offer to take care of the OIDC code's maintenance in future too. You folks all have my MXID and can always poke me if there are future issues about the OIDC code.

@TheOneWithTheBraid TheOneWithTheBraid force-pushed the braid/msc3861-native-oidc branch 3 times, most recently from ff732a4 to 83f4ec1 Compare February 11, 2025 15:33
@TheOneWithTheBraid
Copy link
Contributor Author

Clients might require the following workaround for #2027 : #2027 (comment)

Copy link
Contributor Author

@TheOneWithTheBraid TheOneWithTheBraid left a comment

Choose a reason for hiding this comment

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

Is there any update on review of this PR ? It's now been months and months of waiting and I still did not even get a response on whether you are actually willing to ever accept this contribution. Instead only vaguely related comments.

@TheOneWithTheBraid TheOneWithTheBraid force-pushed the braid/msc3861-native-oidc branch from c483116 to 895bb82 Compare May 15, 2025 11:34
@td-famedly td-famedly self-requested a review May 19, 2025 16:00
@td-famedly
Copy link
Member

td-famedly commented May 19, 2025

helo :3 sorry for the delay. I will now start reading the msc(s) and try to get back to this asap!

(still expecting a marklin set on my bday!)

edit:

It's now been months and months of waiting and I still did not even get a response on whether you are actually willing to ever accept this contribution. Instead only vaguely related comments.

Sorry for the delay, everyone internally has been super busy with "work". I have not heard anything about not accepting this contribution so I will review it with the end goal of getting it merged asap! Again thank you very much for the contribution and sorry for the delay!

@TheOneWithTheBraid TheOneWithTheBraid force-pushed the braid/msc3861-native-oidc branch from 9ad6df5 to 0c90f2b Compare May 20, 2025 08:54
Copy link
Member

@td-famedly td-famedly left a comment

Choose a reason for hiding this comment

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

very draft review, will try to go through the whole flow and look at the client implementation after this

@@ -163,7 +163,6 @@ void main() {
now,
Copy link
Member

Choose a reason for hiding this comment

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

would it be possible to add tests for this?

@@ -0,0 +1,44 @@
import 'package:matrix/matrix.dart';

extension Msc4191AccountManagementExtension on Client {
Copy link
Member

Choose a reason for hiding this comment

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

only a little bit concerned that this is a draft msc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's a draft but a) it doesn't break anything and b) it's so much of a simple MSC, I don't see much regression in it - even if we should happen to adjust the behavior later in time.

@TheOneWithTheBraid TheOneWithTheBraid force-pushed the braid/msc3861-native-oidc branch from f287859 to 48bb3b3 Compare June 12, 2025 07:37
import 'package:matrix/src/utils/crypto/crypto.dart';

extension OidcOauthGrantFlowExtension on Client {
Future<void> oidcAuthorizationGrantFlow({
Copy link
Contributor

Choose a reason for hiding this comment

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

The method name says nothing about what it does? No idea what this is flow. Please switch this to a proper name with dart docs

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm still not happy with this method name. A flow is a thing, not an action but a method is an action so it would better to be more intuitive to use a verb here like: loginWithOidcAuthGrantFlow()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not called oidcLoginAuthorizationGrantFlow. All methods related to OIDC use this prefix and I'd avoid having a gap word such as with in a method name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe the major confusion comes from the work login : Using OIDC you do not "log in" using the matrix dart SDK but you login to your browser and use a token to authenticate. This process is simply not a login. That's why I prefer the technically accurate term oidcAuthorizationGrantFlow.

Copy link
Contributor

@krille-chan krille-chan left a comment

Choose a reason for hiding this comment

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

Looks quite good so far, though I'm not that into the MSCs of Matrix. Will test out if it works with oidc login on FluffyChat soon...

Copy link
Contributor

@krille-chan krille-chan left a comment

Choose a reason for hiding this comment

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

Can you add an example how to actually use this?

Copy link

codecov bot commented Aug 2, 2025

Codecov Report

❌ Patch coverage is 31.08504% with 235 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.13%. Comparing base (e0ff0b8) to head (82ad905).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...4_oidc_oauth_grants/msc2964_oidc_oauth_grants.dart 0.00% 99 Missing ⚠️
...tion/msc2966_oidc_dynamic_client_registration.dart 0.00% 69 Missing ⚠️
...oidc_auth_metadata/msc2965_oidc_auth_metadata.dart 48.14% 28 Missing ⚠️
...account_management/msc4191_account_management.dart 0.00% 11 Missing ⚠️
lib/src/client.dart 81.66% 11 Missing ⚠️
...ifier_syntax/msc1597_matrix_identifier_syntax.dart 0.00% 9 Missing ⚠️
lib/src/database/matrix_sdk_database.dart 82.85% 6 Missing ⚠️
...c3824_oidc_delegation/msc3824_oidc_delegation.dart 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2024      +/-   ##
==========================================
- Coverage   55.71%   55.13%   -0.59%     
==========================================
  Files         146      152       +6     
  Lines       18364    18627     +263     
==========================================
+ Hits        10232    10270      +38     
- Misses       8132     8357     +225     
Files with missing lines Coverage Δ
lib/encryption/utils/bootstrap.dart 81.54% <100.00%> (-0.14%) ⬇️
lib/src/database/database_api.dart 0.00% <ø> (ø)
...c3824_oidc_delegation/msc3824_oidc_delegation.dart 0.00% <0.00%> (ø)
lib/src/database/matrix_sdk_database.dart 90.99% <82.85%> (+0.10%) ⬆️
...ifier_syntax/msc1597_matrix_identifier_syntax.dart 0.00% <0.00%> (ø)
...account_management/msc4191_account_management.dart 0.00% <0.00%> (ø)
lib/src/client.dart 77.15% <81.66%> (-0.19%) ⬇️
...oidc_auth_metadata/msc2965_oidc_auth_metadata.dart 48.14% <48.14%> (ø)
...tion/msc2966_oidc_dynamic_client_registration.dart 0.00% <0.00%> (ø)
...4_oidc_oauth_grants/msc2964_oidc_oauth_grants.dart 0.00% <0.00%> (ø)

Continue to review full report in Codecov by Sentry.

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

@TheOneWithTheBraid
Copy link
Contributor Author

Can you add an example how to actually use this?

I added a README in the MSC folder for client developers to gather an example.

Implements:

- MSC 3861 - Next-generation auth for Matrix, based on OAuth 2.0/OIDC
  - MSC 1597 - Better spec for matrix identifiers
  - MSC 2964 - Usage of OAuth 2.0 authorization code grant and refresh token grant
  - MSC 2965 - OAuth 2.0 Authorization Server Metadata discovery
  - MSC 2966 - Usage of OAuth 2.0 Dynamic Client Registration in Matrix
  - MSC 2967 - API scopes
  - MSC 3824 - OIDC aware clients
  - MSC 4191 - Account management deep-linking

Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
…i to client

Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
Signed-off-by: The one with the braid <info@braid.business>
@TheOneWithTheBraid TheOneWithTheBraid force-pushed the braid/msc3861-native-oidc branch from cff4fd8 to 8ab97e0 Compare August 12, 2025 09:04
/// - [prompt]: the OAuth2.0 authorization prompt. This must be a valid prompt from the [getOidcDiscoveryInformation] as by https://openid.net/specs/openid-connect-prompt-create-1_0.html
Future<void> oidcLoginAuthorizationGrantFlow({
required Completer<OidcCallbackResponse> nativeCompleter,
required String oidcClientId,
Copy link
Contributor

Choose a reason for hiding this comment

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

If oidc is already part of the method name and there is no other clientId we can IMO just name this clientId. afaik other APIs using OIDC treat it the same

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, renamed !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, renamed !

Copy link
Contributor

@krille-chan krille-chan left a comment

Choose a reason for hiding this comment

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

This now contains 22 commits. Can you clean up the commit history please?


Future<Map<String, Object?>?> getOidcAuthMetadata();

Future<void> storeOidcDynamicClientId(String? oidcClientId);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if this is the right approach to create methods for every single thing we store in the Client Database. This is now already messed up as we use getClient and updateClient to update single fields but now for other single fields we have methods. What about just adding one generic method getClientData(ClientData key) and putClientData(ClientData key, value) where ClientData is an enum of all possible fields

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I migrated the String based key-value access on the client box contents to your proposed enum-based approach. I'd suggest to further extend this to a type safe approach including the values of the box but this is way beyond the scope of this OIDC-related PR.

Signed-off-by: The one with the braid <info@braid.business>
@TheOneWithTheBraid
Copy link
Contributor Author

This now contains 22 commits. Can you clean up the commit history please?

You can squash the commits as soon as you want to merge this PR. It does not make sense to rewrite the commit history before review is done. Squashing the commits is a task for the person merging this PR at the end.

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