Skip to content

Conversation

@SangJunBak
Copy link
Contributor

@SangJunBak SangJunBak commented Jan 12, 2026

See commit messages for details.

Decided against using a system parameter for now to gate this feature, mainly because:

  • Feature's already gated by the user having to set their authenticatorkind as Oidc
  • The rigging of system parameters to authentication code is a bit unclear still
  • We're going to rig up OIDC config to system parameters eventually as part of a later phase

Motivation

For an overview of what still needs to be implemented, a summary can be found on Linear (link)

To try:

  1. Follow the quickstart guide to get an authentication server (Ory) running in Docker https://www.ory.com/docs/hydra/self-hosted/quickstart
  2. Once running in docker, run:
bin/environmentd \
-- --oidc-issuer="http://127.0.0.1:4444" \
--listeners-config-path='src/materialized/ci/listener_configs/oidc.json'
  1. In the cloned repo, run the following commands to create a client and get the access token by following the authorization code flow
code_client=$(docker compose -f quickstart.yml exec hydra \
    hydra create client \
    --endpoint http://127.0.0.1:4445 \
    --grant-type authorization_code,refresh_token \
    --response-type code,id_token \
    --format json \
    --scope openid --scope offline --scope profile --scope email\
    --access-token-strategy jwt \
    --redirect-uri http://127.0.0.1:5555/callback)

code_client_id=$(echo $code_client | jq -r '.client_id')
code_client_secret=$(echo $code_client | jq -r '.client_secret')

docker compose -f quickstart.yml exec hydra \
    hydra perform authorization-code \
    --client-id $code_client_id \
    --client-secret $code_client_secret \
    --endpoint http://127.0.0.1:4444/ \
    --port 5555 \
    --scope openid --scope offline --scope profile --scope email
  1. Run `PGPASSWORD="eyJ..." psql -h localhost -p 6875 -U foo@bar.com materialize
demo.mov

Tips for reviewer

Some open questions I had regarding my implementation:

  • Not sure if GenericOidcAuthenticator is the right word for the authenticator. Open to feedback!
  • For the internal_user_metadata change, it felt cleaner to combine it with role_metadata in session.rs. However, I decided against it since it would move the source of truth of the superuser status from the Session > SessionVars > User to Session, something adapter: move user field from Session to SessionVars #17961 was trying to avoid. Furthermore, role_metadata holds information regarding not only its role, but other roles too.

Checklist

  • This PR has adequate test coverage / QA involvement has been duly considered. (trigger-ci for additional test/nightly runs)
  • This PR has an associated up-to-date design doc, is a design doc (template), or is sufficiently small to not require a design.
  • If this PR evolves an existing $T ⇔ Proto$T mapping (possibly in a backwards-incompatible way), then it is tagged with a T-proto label.
  • If this PR will require changes to cloud orchestration or tests, there is a companion cloud PR to account for those changes that is tagged with the release-blocker label (example).
  • If this PR includes major user-facing behavior changes, I have pinged the relevant PM to schedule a changelog post.

@SangJunBak SangJunBak force-pushed the jun/add-system-parameters-for-oidc branch 5 times, most recently from ffdddbc to 63f323e Compare January 14, 2026 17:10
@SangJunBak SangJunBak force-pushed the jun/add-system-parameters-for-oidc branch 8 times, most recently from bb5861a to 90002f0 Compare January 21, 2026 06:34
@SangJunBak SangJunBak force-pushed the jun/add-system-parameters-for-oidc branch 2 times, most recently from b59884a to 8eb54d2 Compare January 21, 2026 08:15
@SangJunBak SangJunBak marked this pull request as ready for review January 21, 2026 08:17
@SangJunBak SangJunBak requested review from a team and ggevay as code owners January 21, 2026 08:17
@SangJunBak SangJunBak removed the request for review from ggevay January 21, 2026 08:17
async fn expired(&mut self) {
// This session never expires - wait forever
// TODO (SangJunBak): Implement expiration.
std::future::pending().await
Copy link
Contributor

Choose a reason for hiding this comment

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

We need expiration support, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! Same motivation for this TODO here #34690 (comment). I figured I'd batch it with the refresh token work

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have tickets for the follow up work? I just don't want us to forget this stuff.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not github issues but I have them recorded in Linear as tasks/subtasks https://linear.app/materializeinc/project/oidc-for-self-managed-3f1f7c14cfb5/issues

// TODO (SangJunBak): Add a configuration variable for the JWKS set and
// a boolean jwksFetchFromIssuer.
let jwks_uri = issuer_url
.join(".well-known/jwks.json")
Copy link
Contributor

Choose a reason for hiding this comment

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

This path, while commonly used, is not part of the OIDC specification. However, .well-known/openid-configuration IS actually part of the specification, and contains a required jwks_uri field. We should use the URI from that field instead.

https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata

let mut validation = Validation::new(header.alg);
validation.set_issuer(&[&self.issuer]);
// TODO (SangJunBak): Validate audience based on configuration.
validation.validate_aud = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

If it isn't configurable, we should default to validating the audience.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added audience as an input here b59f394!

async-trait = "0.1"
jsonwebtoken = "9.3.1"
mz-adapter = { path = "../adapter", default-features = false }
mz-authenticator-types = { path = "../authenticator-types" }
Copy link
Contributor

Choose a reason for hiding this comment

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

Please always set default-features = false for all mz crates. This is needed to avoid pulling in workspace-hack into cloud.

Authenticator::Password(adapter_client) => match creds {
Some(Credentials::Password { username, password }) => {
let auth_response = adapter_client
adapter_client
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 how much it matters, but there was some safety in us using the response from the authenticate call. Now that it isn't being used, there isn't anything preventing us from accidentally not calling it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah do you have more context on this? i.e. who was involved.

there isn't anything preventing us from accidentally not calling it.

What is the "it" you're referring to?

Copy link
Contributor

Choose a reason for hiding this comment

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

adapter_client.authenticate

Copy link
Member

Choose a reason for hiding this comment

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

Ah yeah, there is a risk there. @SangJunBak I think alexs point is well taken, though not urgently something you need to change. His point is that before when we had to return data from the auth response, it meant we couldn't forget to call adapter_client.authenticate. You could have authenticate return some sentinel type that we need to return or we could just be very careful.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd really like a sentinel type added, so we can't forget to call it. It's way to easy to get this wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sgtm!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be addressed!

Copy link
Member

Choose a reason for hiding this comment

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

Yeah this is great, thank you.

// TODO (SangJunBak): Use oidc_auth_enabled boolean and Authenticator::OIDC
// to decide whether or not we want to use OIDC authentication or password
// authentication.
let (_oidc_auth_enabled, options) = extract_oidc_auth_enabled_from_options(options);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this just a placeholder for later? Changing this behavior later is a breaking change.

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 a placeholder for later! More specifically, I don't plan on releasing this to customers in its current state. I wanted to get eyes on it sooner than later and I figured we're effectively gating this feature until the orchestratord changes.

@SangJunBak SangJunBak force-pushed the jun/add-system-parameters-for-oidc branch 6 times, most recently from ef75f70 to b59f394 Compare January 22, 2026 01:35
@SangJunBak
Copy link
Contributor Author

@alex-hunt-materialize Feedback should all be addressed!

@SangJunBak SangJunBak marked this pull request as ready for review January 23, 2026 23:59
_conn_id: &ConnectionId,
client_ip: &Option<IpAddr>,
) -> Result<(RoleId, BTreeMap<String, OwnedVarInput>), AdapterError> {
) -> Result<(RoleId, Option<bool>, BTreeMap<String, OwnedVarInput>), AdapterError> {
Copy link
Member

Choose a reason for hiding this comment

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

could wrap this in a struct or something but 🤷🏼

Copy link
Member

Choose a reason for hiding this comment

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

Ah I was thinking returning a struct like Result<AuthedStartupMessage> to make it a bit easier to read but this also does the trick by making it clearer rather than magic bools.

@SangJunBak SangJunBak force-pushed the jun/add-system-parameters-for-oidc branch 2 times, most recently from c306647 to 3bd127a Compare January 27, 2026 02:17
@SangJunBak SangJunBak requested a review from DAlperin January 27, 2026 02:18
Copy link
Member

@DAlperin DAlperin left a comment

Choose a reason for hiding this comment

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

The specific commits I have been asked to review LGTM

@SangJunBak SangJunBak force-pushed the jun/add-system-parameters-for-oidc branch from 3bd127a to 02f83ca Compare January 27, 2026 18:35
@SangJunBak SangJunBak force-pushed the jun/add-system-parameters-for-oidc branch from 02f83ca to 953bad0 Compare January 28, 2026 06:56
We extract
- authenticate
- validate_access_token

Two methods used to authenticate HTTP and pgwire sessions out of the Frontegg authenticator. The goal is we can reuse these methods for a generic OIDC authenticator, used for self managed SSO
- Create an OIDC authenticator kind and a minimal set of config variables using CLI args
- Implement JWK fetch on validate and also cache by the JWK key id.
I noticed that we were doing this weird round trip of getting internal user metadata from the catalog during authentication, then passing it back when initializing the session. By just doing this on startup, we:
- Remove extraneous code
- Open up ease of creating a unified interface for OIDC clients
- Introduced a new `mz-oidc-mock` package
- Implemented tests for OIDC authentication
- Add functionality to extract `oidc_auth_enabled` from startup options, allowing us to use the password authenticator in the future
Before we were mistakenly fetching from jwks.json instead of getting it from the configuration endpoint
…in authentication

- Updated comments to use backticks for OIDC issuer URL
- Changed password handling in oidc http/ws auth to include the username when validating access tokens.
- Simplified OIDC mock server structure by removing the base URL field
- Remove unneeded assertion on role existence
- Add aud claim
- Introduced `oidc_audience` field in OidcConfig to validate JWT's `aud` claim. This follows the spec in the design doc
- Added OIDC mock server audience claims support
- Added tests for audience validation and when we don't need to
Didn't realize we already had a shared crate!
Due to changed restrictions after a discussion with an external advisor, we decided we no longer need to implement the refresh token flow. However, this also means we no longer have the need for a shared OidcAuthenticator trait.
Before when we had to return internal user metadata data from the auth response, it meant we couldn't forget to call adapter_client.authenticate. By introducing a sentinel type, we make it harder for a developer to. 

We also combine `validate_access_token` into `authenticate` for GenericOidcAuthenticator.
@SangJunBak SangJunBak force-pushed the jun/add-system-parameters-for-oidc branch from 953bad0 to 8462470 Compare January 28, 2026 07:09
@aljoscha aljoscha requested review from a team, aljoscha, ohbadiah and teskje and removed request for ohbadiah January 28, 2026 09:21
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