Access Token Returned as JWE Instead of JWT – How to Force JWT?

I’m working on a Rust-based full-stack app that uses Auth0 + PKCE flow to authenticate users via a Yew SPA frontend.
The project is open-source and available on github
organization: opendrafts-rs-os
repository: openapi-axum-server
branch: [auth0-part2-gui]
dir: gui

:wrench: Project Overview

  • Frontend:
    • Framework: [Yew] (Rust/WASM SPA)
    • Directory: gui/ (separate frontend)
  • Backend:
    • Framework: [Axum]
  • Auth Flow: Authorization Code Flow with PKCE
  • Auth0 app config (via .env file):

env

You add .env

CLIENT_ID="xxx"
DOMAIN="xxx.eu.auth0.com"
REDIRECT_URI="http://localhost:8080/callback"

:red_exclamation_mark:Problem
After the user logs in, I receive an access_token in JWE format, which I cannot decrypt (no private key).
I would like to receive the access_token as a signed JWT (RS256), so I can validate it from the Axum backend using public JWKs.

What I’ve Tried
Used the example app in gui/.

Set up .env with the client config (see above).

Exchanged code using the function below (Yew/wasm code):

pub async fn exchange_code_for_token(
    code: &str,
    client_id: &str,
    domain: &str,
    redirect_uri: &str,
) -> Result<TokenResponse, String> {
    let verifier = web_sys::window()
        .and_then(|w| w.session_storage().ok().flatten())
        .and_then(|s| s.get_item("code_verifier").ok().flatten())
        .ok_or("missing code_verifier in sessionStorage")?;

    let body = format!(
        "grant_type=authorization_code&client_id={client_id}&code={code}\
        &redirect_uri={redirect_uri}&code_verifier={verifier}"
    );

    let url = format!("https://{domain}/oauth/token");

    let builder = Request::post(&url)
        .header("Content-Type", "application/x-www-form-urlencoded")
        .body(body)
        .map_err(|e| format!("{e}"))?;

    let response = builder.send().await.map_err(|e| format!("{e}"))?;

    if !response.ok() {
        let status = response.status();
        let text = response.text().await.unwrap_or_else(|_| "empty".into());
        return Err(format!("status: {status}: {text}"));
    }

    let token: TokenResponse = response.json().await.map_err(|e| format!("JSON: {e}"))?;
    Ok(token)
}

:red_question_mark:My Questions

  1. How do I configure Auth0 (app or API settings) so that the returned access_token is a JWT, not a JWE?
  2. Is it a matter of:
  • changing the API signing algorithm from “RS256” to something else?
  • setting a proper audience during token request?
  1. Can SPAs using PKCE ever receive JWT access tokens, or is JWE the default for security reasons?
  2. Should I enable/disable any “OIDC Conformant” or “Allow Skipping User Consent” options?

:test_tube: Extra Info

  • The access_token returned starts like:
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ...
  • The id_token is a readable JWT – only the access token is JWE.

:folded_hands: All suggestions are welcome!

To receive a signed JWT access_token (RS256) instead of a JWE or opaque token from Auth0 in your Yew SPA, you must define an API in your Auth0 dashboard and then explicitly include that API’s Identifier as the audience parameter in your frontend’s token exchange request to Auth0’s /oauth/token endpoint. This tells Auth0 the access_token is for a specific API, prompting it to issue a self-contained JWT that your Axum backend can validate using Auth0’s public JWKs. Setting the API’s signing algorithm to RS256 is also crucial for this validation. SPAs using PKCE can indeed receive JWT access tokens when an audience is properly specified.

1 Like

Unfortunately, adding the audience and setting up the API still seems insufficient — I’m still receiving a JWE in return.

It wouldn’t be a problem if I could verify it on the backend — in fact, I’d even say it would be safer that way.

Here’s the updated code in the exchange_code_for_token method:

let body = format!(
    "grant_type=authorization_code\
    &client_id={client_id}\
    &code={code}\
    &redirect_uri={redirect_url_url_encoding}\
    &code_verifier={verifier}\
    &audience={audience_url_encoding}",
);