openidconnect-rs icon indicating copy to clipboard operation
openidconnect-rs copied to clipboard

Question on AdditionalClaims for id_token

Open fredrik-jansson-se opened this issue 8 months ago • 3 comments

Thanks for this awesome lib, I wish I had found it earlier, been doing some of these integrations manually..

I'm testing an integration with Azure and I need to access two non standard claims roles and groups that are sent in the ID token.

I can't figure out how to set a custom claim there though, I thought the following would do:

#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct MSClaims {
    groups: Option<Vec<String>>,
    roles: Option<Vec<String>>,
}

impl openidconnect::AdditionalClaims for MSClaims {}

type MSClient<
    HasAuthUrl = openidconnect::EndpointNotSet,
    HasDeviceAuthUrl = openidconnect::EndpointNotSet,
    HasIntrospectionUrl = openidconnect::EndpointNotSet,
    HasRevocationUrl = openidconnect::EndpointNotSet,
    HasTokenUrl = openidconnect::EndpointNotSet,
    HasUserInfoUrl = openidconnect::EndpointNotSet,
> = openidconnect::Client<
    MSClaims,
    openidconnect::core::CoreAuthDisplay,
    openidconnect::core::CoreGenderClaim,
    openidconnect::core::CoreJweContentEncryptionAlgorithm,
    openidconnect::core::CoreJsonWebKey,
    openidconnect::core::CoreAuthPrompt,
    openidconnect::StandardErrorResponse<openidconnect::core::CoreErrorResponseType>,
    openidconnect::core::CoreTokenResponse,
    openidconnect::core::CoreTokenIntrospectionResponse,
    openidconnect::core::CoreRevocableToken,
    openidconnect::core::CoreRevocationErrorResponse,
    HasAuthUrl,
    HasDeviceAuthUrl,
    HasIntrospectionUrl,
    HasRevocationUrl,
    HasTokenUrl,
    HasUserInfoUrl,
>;
  let client = MSClient::from_provider_metadata(
         provider_metadata,
         openidconnect::ClientId::new(CID.to_owned()),
         Some(openidconnect::ClientSecret::new(CS.to_owned())),
     )
     .set_redirect_uri(openidconnect::RedirectUrl::new(
         "http://localhost:40000/oidc_callback".to_owned(),
     )?);

But that leads to the following errors

error[E0599]: the function or associated item `from_provider_metadata` exists for struct `Client<MSClaims, ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ...>`, but its trait bounds were not satisfied
   --> src/main.rs:64:28
    |
64  |     let client = MSClient::from_provider_metadata(
    |                            ^^^^^^^^^^^^^^^^^^^^^^ function or associated item cannot be called due to unsatisfied trait bounds
    |
   ::: /Users/frja/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/oauth2-5.0.0/src/token/mod.rs:598:1
    |
598 | pub struct StandardTokenResponse<EF, TT>
    | ---------------------------------------- doesn't satisfy `_: TokenResponse<MSClaims, CoreGenderClaim, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm>`
    |
    = note: the full type name has been written to '/Users/frja/tmp/sl-test-oidc/tester/target/debug/deps/tester-6ce4f383b7b44458.long-type-15206237441135525973.txt'
    = note: consider using `--verbose` to print the full type name to the console
    = note: the following trait bounds were not satisfied:
            `StandardTokenResponse<IdTokenFields<EmptyAdditionalClaims, EmptyExtraTokenFields, CoreGenderClaim, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm>, CoreTokenType>: TokenResponse<MSClaims, CoreGenderClaim, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm>`

error[E0277]: the trait bound `StandardTokenResponse<IdTokenFields<EmptyAdditionalClaims, EmptyExtraTokenFields, CoreGenderClaim, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm>, CoreTokenType>: TokenResponse<MSClaims, CoreGenderClaim, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm>` is not satisfied
   --> src/main.rs:64:18
    |
64  |     let client = MSClient::from_provider_metadata(
    |                  ^^^^^^^^ unsatisfied trait bound
    |
    = help: the trait `TokenResponse<MSClaims, CoreGenderClaim, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm>` is not implemented for `StandardTokenResponse<IdTokenFields<..., ..., ..., ..., ...>, ...>`
            but trait `TokenResponse<EmptyAdditionalClaims, CoreGenderClaim, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm>` is implemented for it
    = help: for that trait implementation, expected `EmptyAdditionalClaims`, found `MSClaims`
note: required by a bound in `openidconnect::Client`
   --> /Users/frja/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/openidconnect-4.0.0/src/client.rs:121:9
    |
93  | pub struct Client<
    |            ------ required by a bound in this struct
...
121 |     TR: TokenResponse<AC, GC, JE, K::SigningAlgorithm>,
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Client`

Some errors have detailed explanations: E0277, E0599.
For more information about an error, try `rustc --explain E0277`.

Not sure how to dig myself out of this hole :)

fredrik-jansson-se avatar Apr 03 '25 09:04 fredrik-jansson-se

the EmptyAdditionalClaims in the error makes me think you're using CoreClient or one of the CoreIdToken* types somewhere. is it possible client is being coerced to a CoreClient somewhere after the snippet above?

ramosbugs avatar Apr 04 '25 03:04 ramosbugs

Thanks for getting back to me!

Unfortunately I can reproduce is with this minimal (I hope) code:

 async fn test() {
      let http_client = openidconnect::reqwest::ClientBuilder::new()
          .build().unwrap();

      let provider_metadata = openidconnect::core::CoreProviderMetadata::discover_async(
          openidconnect::IssuerUrl::new("".to_owned()).unwrap(),
          &http_client,
      )
      .await.unwrap();

     let client = MSClient::from_provider_metadata(
          provider_metadata,
          openidconnect::ClientId::new("".to_owned()),
          Some(openidconnect::ClientSecret::new("".to_owned())),
      );
  }

fredrik-jansson-se avatar Apr 04 '25 06:04 fredrik-jansson-se

Turns out I had to fix the TokenResponse as well.

The following typedefs works well.

#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct MSClaims {
    groups: Option<Vec<String>>,
    roles: Option<Vec<String>>,
}

impl openidconnect::AdditionalClaims for MSClaims {}

type MSTokenFields = openidconnect::IdTokenFields<
    MSClaims,
    openidconnect::EmptyExtraTokenFields,
    openidconnect::core::CoreGenderClaim,
    openidconnect::core::CoreJweContentEncryptionAlgorithm,
    openidconnect::core::CoreJwsSigningAlgorithm,
>;

pub type MSTokenResponse =
    openidconnect::StandardTokenResponse<MSTokenFields, openidconnect::core::CoreTokenType>;

type MSClient<
    HasAuthUrl = openidconnect::EndpointNotSet,
    HasDeviceAuthUrl = openidconnect::EndpointNotSet,
    HasIntrospectionUrl = openidconnect::EndpointNotSet,
    HasRevocationUrl = openidconnect::EndpointNotSet,
    HasTokenUrl = openidconnect::EndpointNotSet,
    HasUserInfoUrl = openidconnect::EndpointNotSet,
> = openidconnect::Client<
    MSClaims,
    openidconnect::core::CoreAuthDisplay,
    openidconnect::core::CoreGenderClaim,
    openidconnect::core::CoreJweContentEncryptionAlgorithm,
    openidconnect::core::CoreJsonWebKey,
    openidconnect::core::CoreAuthPrompt,
    openidconnect::StandardErrorResponse<openidconnect::core::CoreErrorResponseType>,
    MSTokenResponse,
    openidconnect::core::CoreTokenIntrospectionResponse,
    openidconnect::core::CoreRevocableToken,
    openidconnect::core::CoreRevocationErrorResponse,
    HasAuthUrl,
    HasDeviceAuthUrl,
    HasIntrospectionUrl,
    HasRevocationUrl,
    HasTokenUrl,
    HasUserInfoUrl,
>;

Let me know if you want me to document this somehow

fredrik-jansson-se avatar Apr 08 '25 08:04 fredrik-jansson-se