For training purposes I am trying to implement a google Oauth solution in axum, but I have run into a problem that I am having a hard time wrapping my head around.
For context i have a simple server setup as follows:
use axum::{
routing::{get},
Router,
};
use http::Method;
use std::net::SocketAddr;
use tower_http::cors::{Any, CorsLayer};
use crate::auth::google_oauth_handler;
mod auth;
#[tokio::main]
async fn main() -> anyhow::Result<()>{
let cors = CorsLayer::new()
// allow `GET` and `POST` when accessing the resource
.allow_methods([Method::GET, Method::POST, Method::OPTIONS, Method::DELETE])
.allow_headers(Any)
// allow requests from any origin
.allow_origin(Any);
let app = Router::new()
.route("/api/hello", get(root))
.route("/api/sessions/oauth/google", get(google_oauth_handler))
.layer(cors);
let addr = SocketAddr::from(([0,0,0,0],8000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await?;
Ok(())
}
async fn root() -> &'static str {
"Hello World!"
}
The Oauth from google is setup so that a frontend contains a link with the information required, and a client is prompted to the google approval consent screen. Upon aproval the client is redirected back to the server with a code in the query, that the server retrieves, picks up the access and ID tokens, and uses those to request the user information through the google userinfo API.
This workflow has worked when using Actix_web, however when trying to move the solution to Axum I get the following error:
error[E0277]: the trait bound `fn(Query<AccessCode>) -> impl Future<Output = impl
IntoResponse> {google_oauth_handler}: Handler<_, _>` is not satisfied
--> src\main.rs:26:50
|
26 | .route("/api/sessions/oauth/google", get(google_oauth_handler))
| --- ^^^^^^^^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(Query<AccessCode>) -> impl Future<Output = impl IntoResponse> {google_oauth_handler}`
| |
| required by a bound introduced by this call
|
= help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
note: required by a bound in `axum::routing::get`
--> C:\Users\user\.cargo\registry\src\github.com-1ecc6299db9ec823\axum-0.5.17\src\routing\method_routing.rs:397:1
|
397 | top_level_handler_fn!(get, GET);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `get`
= note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
The code used to run the auhentication authentication workflow is as follows:
use axum::{Json};
use axum::response::IntoResponse;
use axum::http::StatusCode;
use reqwest::{Client, Url};
use serde::{Deserialize};
use std::error::Error;
use axum_macros::debug_handler;
#[derive(Deserialize, Debug)]
pub struct AccessCode {
pub code: String,
}
#[derive(Deserialize, Debug)]
pub struct OAuthResponse {
pub access_token: String,
pub id_token: String,
}
#[derive(Deserialize,Debug)]
pub struct GoogleUserResult {
pub id: String,
pub email: String,
pub verified_email: bool,
pub name: String,
pub given_name: String,
pub family_name: String,
pub picture: String,
pub locale: String,
}
pub async fn google_oauth_handler(query: Query<AccessCode>) -> impl IntoResponse {
let access_code: AccessCode = query.0;
println!("{:?}", access_code.code);
if access_code.code.is_empty() {
return (StatusCode::NOT_FOUND, Json("Code was not found".to_string()));
}
let token_response = request_token(access_code).await;
if token_response.is_err() {
let message = token_response.err().unwrap().to_string();
return (StatusCode::NOT_FOUND, Json(message));
}
let token_response = token_response.unwrap();
let google_user = get_google_user(&token_response.access_token, &token_response.id_token).await;
if google_user.is_err() {
let message = google_user.err().unwrap().to_string();
return (StatusCode::NOT_FOUND, Json(message));
}
let google_user = google_user.unwrap();
(StatusCode::OK, Json(token_response.id_token))
}
pub async fn request_token(access_code: AccessCode) -> Result<OAuthResponse, Box<dyn Error>> {
let redirect_url = "http://localhost:8000/api/sessions/oauth/google";
let client_secret = "superSecret";
let client_id = "someID";
let root_url = "https://oauth2.googleapis.com/token";
let client = Client::new();
let params = [
("grant_type", "authorization_code"),
("redirect_uri", redirect_url),
("client_id", client_id),
("code", &access_code.code),
("client_secret", client_secret),
("scope", ""),
];
let response = client.post(root_url).form(¶ms).send().await?;
if response.status().is_success() {
let oauth_response = response.json::<OAuthResponse>().await?;
Ok(oauth_response)
} else {
let message = "An error occurred while trying to retrieve access token.";
Err(From::from(message))
}
}
pub async fn get_google_user(access_token: &str, id_token: &str) -> Result<GoogleUserResult, Box<dyn Error>> {
let client = Client::new();
let mut url = Url::parse("https://www.googleapis.com/userinfo/v2/me").unwrap();
url.query_pairs_mut().append_pair("alt", "json");
url.query_pairs_mut()
.append_pair("access_token", access_token);
let response = client.get(url).bearer_auth(id_token).send().await?;
if response.status().is_success() {
let user_info = response.json::<GoogleUserResult>().await?;
println!("{:?}", user_info);
Ok(user_info)
} else {
let message = "An error occurred while trying to retrieve user information.";
Err(From::from(message))
}
}
What frustrates me a bit is that retrieval of the access and ID tokens works if it is the only function call in the Oauth handler, but when the user data retrieval is added it fails on the trait bounds.
Any suggestions as to what is happening, and how i resolve it? Any pointers in terms of reading i could do to better understand the issue would be great.
//////////////////////////////////////////////////
With the suggested addition of the #[debug_handler]
i was able to get the following error message. However I am still not sure what is causing the error or how to begin the preparation for fixing it.
Any suggestions?
error[E0107]: trait takes 1 generic argument but 3 generic arguments were supplied
--> src\auth.rs:33:42
|
32 | #[debug_handler]
| ---------------- help: remove these generic arguments
33 | pub async fn google_oauth_handler(query: Query<AccessCode>) -> impl IntoResponse {
| ^^^^^ expected 1 generic argument
|
note: trait defined here, with 1 generic parameter: `B`
--> C:\Users\user\.cargo\registry\src\github.com-1ecc6299db9ec823\axum-core-0.2.9\src\extract\mod.rs:66:11
|
66 | pub trait FromRequest<B>: Sized {
| ^^^^^^^^^^^ -
For more information about this error, try `rustc --explain E0107`.
error: could not compile `axum_auth` due to previous error