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::Mutexcannot be held across anawaitpoint, which is why you're seeing thetype is not Sendcompile error. You have two options:Drop the lock before you reach an
awaitpoint. You only useverifiersto getpkce_verifier, so you could just inline it:That is somewhat ugly, so you could also just manually call
drop()on the lock:Alternatively, you could create and use the lock within an inner block scope, which will automatically drop the lock when the block exits scope:
You could use an async-aware lock, like
futures::lock::Mutex. If you think you will need to use a mutex across anawaitpoint 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 anawaitpoint.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 aHashMap, you should use pattern matching or built-inOption-to-Resultfunctions Rust provides likeok_or().