I wanted to implement an authorization code flow in axum with PKCE. Therefore I have to hand over the generated PKCE code verifier to the callback route in order to exchange the code for a token, so that I can proceed logging my user in with a session and such.
Right now I mainly use a hammer, just to describe why the solution might look like it does:
#[derive(Clone)]
struct AppState {
db: PgPool,
oauth_client: BasicClient,
verifiers: Arc<Mutex<HashMap<String, String>>>,
}
#[debug_handler]
async fn callback(
State(state): State<AppState>,
Query(auth_request): Query<AuthRequest>,
) -> Result<impl IntoResponse, impl IntoResponse> {
let auth_request = auth_request;
let verifiers = state.verifiers.lock().unwrap();
let pkce_verifier = verifiers.get(&auth_request.state).unwrap().into();
let pkce_verifier = PkceCodeVerifier::new(pkce_verifier);
let _token_result = match state
.oauth_client
.exchange_code(AuthorizationCode::new(auth_request.code))
.set_pkce_verifier(pkce_verifier)
.request_async(async_http_client)
.await
{
Ok(res) => res,
Err(e) => {
error!("could not exchange code: {e}");
return Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string()));
}
};
Ok(Redirect::temporary("/"))
}
the error is as follows:
--> src/main.rs:134:10
|
125 | let verifiers = state.verifiers.lock().unwrap();
| --------- has type `std::sync::MutexGuard<'_, std::collections::HashMap<std::string::String, std::string::String>>` which is not `Send`
134 | .await
| ^^^^^ await occurs here, with `verifiers` maybe used later
I understand that the lock on the mutex is hold too long. I've tried to understand lots of similar problems, but I am not able to understand this yet.
Rust's std::sync::Mutex
cannot be held across an await
point, which is why you're seeing the type is not Send
compile error. You have two options:
Drop the lock before you reach an await
point. You only use verifiers
to get pkce_verifier
, so you could just inline it:
let pkce_verifier = state
.verifiers
.lock()
.unwrap()
.get(&auth_request.state)
.unwrap()
.into();
That is somewhat ugly, so you could also just manually call drop()
on the lock:
let verifiers = state.verifiers.lock().unwrap();
let pkce_verifier = verifiers.get(&auth_request.state).unwrap().into();
drop(verifiers);
Alternatively, you could create and use the lock within an inner block scope, which will automatically drop the lock when the block exits scope:
let pkce_verifier = {
let verifiers = state.verifiers.lock().unwrap();
verifiers.get(&auth_request.state).unwrap().into()
};
You could use an async-aware lock, like futures::lock::Mutex
. If you think you will need to use a mutex across an await
point in the future, you could just switch to an async-aware mutex. Otherwise, the standard library mutex will work as long as the lock isn't held across an await
point.
As a side note, it's bad practice to call .unwrap()
on objects unless you're sure the field exists. Especially in the case of a HashMap
, you should use pattern matching or built-in Option
-to-Result
functions Rust provides like ok_or()
.