1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
//! Implementation of http bearer authentication
//!
//! See [AuthBearer] for the most commonly-used data structure
use crate::{Rejection, ERR_CHARS, ERR_DEFAULT, ERR_MISSING, ERR_WRONG_BEARER};
use async_trait::async_trait;
use axum_core::extract::FromRequestParts;
use http::{header::AUTHORIZATION, request::Parts, StatusCode};
/// Bearer token extractor which contains the innards of a bearer header as a string
///
/// This is enabled via the `auth-bearer` feature
///
/// # Example
///
/// This structure can be used like any other axum extractor:
///
/// ```no_run
/// use axum_auth::AuthBearer;
///
/// /// Handler for a typical [axum] route, takes a `token` and returns it
/// async fn handler(AuthBearer(token): AuthBearer) -> String {
/// format!("Found a bearer token: {}", token)
/// }
/// ```
///
/// # Errors
///
/// There are a few errors which this extractor can make. By default, all invalid responses are `400 BAD REQUEST` with one of these messages:
///
/// - \`Authorization\` header must be a bearer token – Somebody tried to but basic auth here instead of bearer
/// - \`Authorization\` header is missing – The header was required but it wasn't found
/// - \`Authorization\` header contains invalid characters – The header couldn't be processed because of invalid characters
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AuthBearer(pub String);
#[async_trait]
impl<B> FromRequestParts<B> for AuthBearer
where
B: Send + Sync,
{
type Rejection = Rejection;
async fn from_request_parts(req: &mut Parts, _: &B) -> Result<Self, Self::Rejection> {
Self::decode_request_parts(req)
}
}
impl AuthBearerCustom for AuthBearer {
const ERROR_CODE: StatusCode = ERR_DEFAULT;
const ERROR_OVERWRITE: Option<&'static str> = None;
fn from_header(contents: &str) -> Self {
Self(contents.to_string())
}
}
/// Custom extractor trait for bearer allowing you to implement custom responses
///
/// This is enabled via the `auth-bearer` feature
///
/// # Usage
///
/// To create your own bearer auth extractor using this crate, you have to:
///
/// 1. Make the extractor struct, something like `struct Example(String);`
/// 2. Implement [FromRequestParts] that links to step 3, copy and paste this from the example below
/// 3. Implement [AuthBearerCustom] to generate your extractor with your custom options, see the example below
///
/// Once you've completed these steps, you should have a new extractor which is just as easy to use as [AuthBearer] but has all of your custom configuration options inside of it!
///
/// # Example
///
/// This is what a typical custom extractor should look like in full, copy-paste this and edit it:
///
/// ```rust
/// use async_trait::async_trait;
/// use axum::extract::FromRequestParts;
/// use axum_auth::{AuthBearerCustom, Rejection};
/// use http::{request::Parts, StatusCode};
///
/// /// Your custom bearer auth returning a fun 418 for errors
/// struct MyCustomBearerAuth(String);
///
/// // this is where you define your custom options
/// impl AuthBearerCustom for MyCustomBearerAuth {
/// const ERROR_CODE: StatusCode = StatusCode::IM_A_TEAPOT; // <-- define custom status code here
/// const ERROR_OVERWRITE: Option<&'static str> = None; // <-- define overwriting message here
///
/// fn from_header(contents: &str) -> Self {
/// Self(contents.to_string())
/// }
/// }
///
/// // this is just boilerplate, copy-paste this
/// #[async_trait]
/// impl<B> FromRequestParts<B> for MyCustomBearerAuth
/// where
/// B: Send + Sync,
/// {
/// type Rejection = Rejection;
///
/// async fn from_request_parts(parts: &mut Parts, _: &B) -> Result<Self, Self::Rejection> {
/// Self::decode_request_parts(parts)
/// }
/// }
/// ```
///
/// Some notes about this example for some more insight:
///
/// - There's no reason for the [FromRequestParts] to ever change out of this pattern unless you're doing something special
/// - It's recommended to use the `struct BearerExample(String);` pattern because it makes using it from routes easy
pub trait AuthBearerCustom: Sized {
/// Error code to use instead of the typical `400 BAD REQUEST` error
const ERROR_CODE: StatusCode;
/// Message to overwrite all default ones with if required, leave as [None] ideally
const ERROR_OVERWRITE: Option<&'static str>;
/// Converts provided header contents to new instance of self; you need to implement this
///
/// # Example
///
/// With the typical `struct BearerExample(String);` pattern of structures, this can be implemented like so:
///
/// ```rust
/// use axum_auth::AuthBearerCustom;
/// use http::StatusCode;
///
/// struct BearerExample(String);
///
/// impl AuthBearerCustom for BearerExample {
/// const ERROR_CODE: StatusCode = StatusCode::BAD_REQUEST;
/// const ERROR_OVERWRITE: Option<&'static str> = None;
///
/// fn from_header(contents: &str) -> Self {
/// Self(contents.to_string())
/// }
/// }
/// ```
///
/// All this method does is let you put the automatically contents of the header into your resulting structure.
fn from_header(contents: &str) -> Self;
/// Decodes bearer token content into new instance of self from axum body parts; this is automatically implemented
fn decode_request_parts(req: &mut Parts) -> Result<Self, Rejection> {
// Get authorization header
let authorization = req
.headers
.get(AUTHORIZATION)
.ok_or((Self::ERROR_CODE, ERR_MISSING))?
.to_str()
.map_err(|_| (Self::ERROR_CODE, ERR_CHARS))?;
// Check that its a well-formed bearer and return
let split = authorization.split_once(' ');
match split {
// Found proper bearer
Some((name, contents)) if name == "Bearer" => Ok(Self::from_header(contents)),
// Found empty bearer; sometimes request libraries format them as this
_ if authorization == "Bearer" => Ok(Self::from_header("")),
// Found nothing
_ => Err((Self::ERROR_CODE, ERR_WRONG_BEARER)),
}
}
}