ramosbugs / openidconnect-rs

OpenID Connect Library for Rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

invalid grant type when trying with google

chris-kruining opened this issue · comments

I am trying to implement SSO for a leptos app I am making but I am stuck at the code exchange phase. I've tried a lot of things but am basically about to give up.

I have the following code:

async fn get_client() -> Result<MyClient, anyhow::Error> {
    let provider_metadata = CoreProviderMetadata::discover_async(
        IssuerUrl::new("https://accounts.google.com".to_string())?,
        async_http_client,
    ).await?;

    Ok(
        CoreClient::from_provider_metadata(
            provider_metadata,
            ClientId::new("***".to_string()),
            Some(ClientSecret::new("***".to_string())),
        )
        .set_redirect_uri(RedirectUrl::new("http://localhost:3000/auth/redirect".to_string())?)
    )
}

pub async fn start() -> Result<(url::Url, openidconnect::Nonce, openidconnect::PkceCodeVerifier), anyhow::Error> {
    let client: MyClient = get_client().await?;

    let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();

    let (auth_url, _, nonce) = client
        .authorize_url(
            CoreAuthenticationFlow::AuthorizationCode,
            CsrfToken::new_random,
            Nonce::new_random,
        )
        // Set the desired scopes.
        .add_scope(Scope::new("email".to_string()))
        // .add_scope(Scope::new("read".to_string()))
        // .add_scope(Scope::new("write".to_string()))
        // Set the PKCE code challenge.
        .set_pkce_challenge(pkce_challenge)
        .url();

    Ok((auth_url, nonce, pkce_verifier))
}

pub async fn exchange(code: String, nonce: String, pkce_verifier: String) -> Result<(), anyhow::Error> {
    use openidconnect::{ TokenResponse, PkceCodeVerifier };

    log!("{{ code: {},\nnonce: {},\npkce: {} }}", code, nonce, pkce_verifier);

    let client = get_client().await?;

    let token_response = client
        .exchange_code(AuthorizationCode::new(code))
        .add_extra_param("client_id", "***")
        .add_extra_param("client_secret", "***")
        .set_pkce_verifier(PkceCodeVerifier::new(pkce_verifier))
        .request_async(async_http_client)
        .await?;

    let _id_token = token_response.id_token().ok_or_else(|| anyhow!("Server did not return an ID token"))?;

    Ok(())
}

but whenever I call exchange I get a 400 response from google
I have notices a couple of things. According to google I need the client id and secret as fields in the post data, those are missing (I believe those are encoded into the authorization header?), so I added those in the dirty way with the extra params. another thing I noticed is that the code I have is a lot longer than the one shown in the google documentation, although I reckon this is not a problem.

image
image
image

p.s. I redacted the client id and secret for hopefully obvious reasons

Hmm... the Google example seems to work with the client credentials in the Authorization header rather than the body, but you can change this behavior by calling client.set_auth_type(AuthType::RequestBody).

The other main difference I see with the Google example is PKCE. I would try temporarily disabling PKCE and making sure it works in case you're somehow passing in a mismatched PKCE verifier.

Since you included a browser devtools screenshot, does this mean you're using the crate via WASM? It should work using WASM, but I haven't specifically tested WASM against Google, so it's possible there's a browser interoperability quirk I'm not aware of. Although, if this happening inside a browser, it probably doesn't make sense to use a client secret since a browser app isn't able to keep a secret away from end users. I believe Google lets you specify the app type during registration so that it doesn't assign a client secret for public apps.

I am indeed running in WASM. I've also tried to run it without the PKCE, but I have the same result

Did you try set_auth_type? The 400 error text should at least change in that scenario since there should no longer be an Authorization header. The updated error will hopefully point us in the right direction.

sorry for the late reaction.

I did try that yes, but it didn't make a difference as I had already managed to move those values by setting custom values. Which I now no longer need to do.

the thing I find most weird though is that the request seems fine. but I don't get the error, like there isn't a Authorization header, so there are no credentials in the headers as far as I can tell

the missing Authorization header could be a CORS issue that might be solved by calling fetch_credentials_include on the reqwest client passed into this crate. See also https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#sending_a_request_with_credentials_included

What would an example code look like to pass a customized reqwest module? The docs says impl this trait but it's very vague.

I created a fullstack Rust/WASM example here: https://github.com/dakom/dominator-workers-fluent-auth

Frontend is using Dominator, but the oauth/openid part happens on the backend, using Cloudflare Workers in Rust/WASM

I know this is not a pretty solution, but it works: a custom http_client impl using native web_sys Request/Response types. No live demo there but I've tested with beta client id/secrets w/ both Google and Facebook:

https://github.com/dakom/dominator-workers-fluent-auth/blob/5b78605c10e44914d1164951994acc0816909ec6/workers/api/src/auth/handler/openid.rs#L161