mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-24 00:00:16 +02:00
Use 308 instead of 301 for trailing slash redirects (#682)
* Use 308 status instead of 301 when redirecting For redirects resulting from requests to paths with a trailing slash, use 308 instead of 301 to prevent non-GET requests (POST, PUT, etc) from being changed to GET. For example, (assuming a route for /path is defined)... - Old behavior results in: POST /path/ -> GET /path - New behavior results in: POST /path/ -> POST /path Fixes #681 * Add deprecation notice to found() Deprecates found() due to its use of HTTP 302 * rustfmt * Use dedicated redirect method Use Redirect::permanent instead of re-implementing its functionality * Remove deprecated method from example Replace usages of Redirect:found with Redirect::to and Redirect::temporary as appropriate * Fix panic in oauth example Previously the example would panic if a request was made without the `Cookie` header. Now the user is redirected to the login page as expected. * Update CHANGELOG * Revert pub TypedheaderRejection fields * Fix clippy lint * cargo fmt * Fix CHANGELOG link * Adhere to implicit line length limit
This commit is contained in:
@@ -15,3 +15,4 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
# Use Rustls because it makes it easier to cross-compile on CI
|
||||
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "json"] }
|
||||
headers = "0.3"
|
||||
http = "0.2"
|
||||
|
||||
+25
-21
@@ -1,20 +1,26 @@
|
||||
//! Example OAuth (Discord) implementation.
|
||||
//!
|
||||
//! Run with
|
||||
//!
|
||||
//! 1) Create a new application at <https://discord.com/developers/applications>
|
||||
//! 2) Visit the OAuth2 tab to get your CLIENT_ID and CLIENT_SECRET
|
||||
//! 3) Add a new redirect URI (for this example: `http://127.0.0.1:3000/auth/authorized`)
|
||||
//! 4) Run with the following (replacing values appropriately):
|
||||
//! ```not_rust
|
||||
//! CLIENT_ID=123 CLIENT_SECRET=secret cargo run -p example-oauth
|
||||
//! CLIENT_ID=REPLACE_ME CLIENT_SECRET=REPLACE_ME cargo run -p example-oauth
|
||||
//! ```
|
||||
|
||||
use async_session::{MemoryStore, Session, SessionStore};
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{Extension, FromRequest, Query, RequestParts, TypedHeader},
|
||||
extract::{
|
||||
rejection::TypedHeaderRejectionReason, Extension, FromRequest, Query, RequestParts,
|
||||
TypedHeader,
|
||||
},
|
||||
http::{header::SET_COOKIE, HeaderMap},
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
routing::get,
|
||||
AddExtensionLayer, Router,
|
||||
};
|
||||
use http::header;
|
||||
use oauth2::{
|
||||
basic::BasicClient, reqwest::async_http_client, AuthUrl, AuthorizationCode, ClientId,
|
||||
ClientSecret, CsrfToken, RedirectUrl, Scope, TokenResponse, TokenUrl,
|
||||
@@ -22,13 +28,6 @@ use oauth2::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{env, net::SocketAddr};
|
||||
|
||||
// Quick instructions:
|
||||
// 1) create a new application at https://discord.com/developers/applications
|
||||
// 2) visit the OAuth2 tab to get your CLIENT_ID and CLIENT_SECRET
|
||||
// 3) add a new redirect URI (For this example: http://localhost:3000/auth/authorized)
|
||||
// 4) AUTH_URL and TOKEN_URL may stay the same for discord.
|
||||
// More information: https://discord.com/developers/applications/792730475856527411/oauth2
|
||||
|
||||
static COOKIE_NAME: &str = "SESSION";
|
||||
|
||||
#[tokio::main]
|
||||
@@ -39,7 +38,7 @@ async fn main() {
|
||||
}
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// `MemoryStore` just used as an example. Don't use this in production.
|
||||
// `MemoryStore` is just used as an example. Don't use this in production.
|
||||
let store = MemoryStore::new();
|
||||
|
||||
let oauth_client = oauth_client();
|
||||
@@ -64,8 +63,8 @@ async fn main() {
|
||||
|
||||
fn oauth_client() -> BasicClient {
|
||||
// Environment variables (* = required):
|
||||
// *"CLIENT_ID" "123456789123456789";
|
||||
// *"CLIENT_SECRET" "rAn60Mch4ra-CTErsSf-r04utHcLienT";
|
||||
// *"CLIENT_ID" "REPLACE_ME";
|
||||
// *"CLIENT_SECRET" "REPLACE_ME";
|
||||
// "REDIRECT_URL" "http://127.0.0.1:3000/auth/authorized";
|
||||
// "AUTH_URL" "https://discord.com/api/oauth2/authorize?response_type=code";
|
||||
// "TOKEN_URL" "https://discord.com/api/oauth2/token";
|
||||
@@ -119,7 +118,7 @@ async fn discord_auth(Extension(client): Extension<BasicClient>) -> impl IntoRes
|
||||
.url();
|
||||
|
||||
// Redirect to Discord's oauth service
|
||||
Redirect::found(auth_url.to_string().parse().unwrap())
|
||||
Redirect::to(auth_url.to_string().parse().unwrap())
|
||||
}
|
||||
|
||||
// Valid user session required. If there is none, redirect to the auth page
|
||||
@@ -138,12 +137,12 @@ async fn logout(
|
||||
let session = match store.load_session(cookie.to_string()).await.unwrap() {
|
||||
Some(s) => s,
|
||||
// No session active, just redirect
|
||||
None => return Redirect::found("/".parse().unwrap()),
|
||||
None => return Redirect::to("/".parse().unwrap()),
|
||||
};
|
||||
|
||||
store.destroy_session(session).await.unwrap();
|
||||
|
||||
Redirect::found("/".parse().unwrap())
|
||||
Redirect::to("/".parse().unwrap())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -192,14 +191,14 @@ async fn login_authorized(
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(SET_COOKIE, cookie.parse().unwrap());
|
||||
|
||||
(headers, Redirect::found("/".parse().unwrap()))
|
||||
(headers, Redirect::to("/".parse().unwrap()))
|
||||
}
|
||||
|
||||
struct AuthRedirect;
|
||||
|
||||
impl IntoResponse for AuthRedirect {
|
||||
fn into_response(self) -> Response {
|
||||
Redirect::found("/auth/discord".parse().unwrap()).into_response()
|
||||
Redirect::temporary("/auth/discord".parse().unwrap()).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,8 +217,13 @@ where
|
||||
|
||||
let cookies = TypedHeader::<headers::Cookie>::from_request(req)
|
||||
.await
|
||||
.expect("could not get cookies");
|
||||
|
||||
.map_err(|e| match *e.name() {
|
||||
header::COOKIE => match e.reason() {
|
||||
TypedHeaderRejectionReason::Missing => AuthRedirect,
|
||||
_ => panic!("unexpected error getting Cookie header(s): {}", e),
|
||||
},
|
||||
_ => panic!("unexpected error getting cookies: {}", e),
|
||||
})?;
|
||||
let session_cookie = cookies.get(COOKIE_NAME).ok_or(AuthRedirect)?;
|
||||
|
||||
let session = store
|
||||
|
||||
Reference in New Issue
Block a user