Rocket OAuth2 state cookie mismatch when calling API endpoint after authorization

66 Views Asked by At

I am writing a fullstack application and I have decided to use svelte as the frontend with a Rocket rust backend:

struct QuickbooksAuth;

#[get("/login/quickbooks")]
fn quickbooks_login(oauth2: OAuth2<QuickbooksAuth>, cookies: &CookieJar<'_>) -> Redirect {
    oauth2
        .get_redirect(cookies, &["com.intuit.quickbooks.accounting"])
        .unwrap()
}

#[get("/auth/quickbooks")]
fn quickbooks_callback(token: TokenResponse<QuickbooksAuth>, cookies: &CookieJar<'_>) -> Redirect {
    cookies.add_private(
        // Todo encrypt this before saving to a cookie
        Cookie::build(("qb_access_token", token.access_token().to_string()))
            .same_site(rocket::http::SameSite::Lax)
            .build(),
    );
    Redirect::to("http://localhost:5173/") // The home page of the frontend
}

struct QBAccessToken(String);

#[rocket::async_trait]
impl<'r> FromRequest<'r> for QBAccessToken {
    type Error = ();
    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        request
            .cookies()
            .get_private("qb_access_token")
            .map(|f| QBAccessToken(f.value().to_owned()))
            .or_forward(rocket::http::Status::Unauthorized)
    }
}

// The endpoints that use the access token
#[get("/?<claim_number>&<get_qb>&<get_sb>")]
async fn get_claim(
    claim_number: String,
    get_qb: bool,
    get_sb: bool,
    qb: &State<Quickbooks>,
    sp: &State<ClaimHandler>,
    access_token: QBAccessToken, // Need this for api calls
) -> Result<HAInvoice, ServiceBooksError> {
    ...
}

and the svelte frontend has a login button that links to the login endpoint:

<a
    class="titlebar-button"
    id="titlebar-qb-login"
    style="right:0px"
    href="http://127.0.0.1:7777/login/quickbooks"
> <img src="some_logo.svg"> 
</a>

and then tries to use that when fetching the api later:

const url = new URL("http://127.0.0.1:7777/claim");

url.searchParams.append("claim_number", claimNumber);
url.searchParams.append("get_qb", getQb);
url.searchParams.append("get_sb", getSb);

try {
  loading = true;
  const response = await fetch(url);
  const data = await response.text();
  claim = data;
} catch (error) {
  notifications.danger(error.message, 2000);
} finally {
  loading = false;
}

That way the access token is only used by the backend and the frontend isn't exposed to it, which I thought would be more secure. When I go to the http://localhost:7777/login/quickbooks endpoint manually it goes through and redirects to the frontend without a problem, but any subsequent api calls are blocked for being unauthorized, and when trying the link from the frontend it throws a bad request error on the auth/quickbooks endpoint for the state having a mismatch:

Matched: (quickbooks_callback) GET /auth/quickbooks
 ERROR rocket_oauth2             > The OAuth2 state cookie was missing. It may have been blocked by the client?
 WARN  servicebooks_server::_    > Request guard `TokenResponse < QuickbooksAuth >` failed: Error { kind: ExchangeFailure, source: Some("The OAuth2 state returned from the server did match the stored state.") }.
 INFO  rocket::server::_         > Outcome: Error(400 Bad Request)
 WARN  rocket::server::_         > No 400 catcher registered. Using Rocket default.

I've already disabled anything blocking cookies in my browser and I've tried clearing the cache and restarting everything I can think of. I suspect this is because the cookies from the /login endpoint are not being retained and passed along. How can I ensure the OAuth state and cookies are maintained throughout the authorization flow and API calls from my Svelte frontend? Do I need to manually save and send the cookie with each request? Or, is this not how OAuth should be used? if so, is there a simple way to convert what I have now to something that is more in line with what's intended?

Edit: I am using the rocket_oauth2 crate to handle the oauth credentials: https://docs.rs/rocket_oauth2/latest/rocket_oauth2/

0

There are 0 best solutions below