From c38b7bf88cf12520b3a1024af108cf03007aeaf5 Mon Sep 17 00:00:00 2001 From: Ryo Nakaya Date: Sat, 13 Dec 2025 15:15:43 +0900 Subject: [PATCH 1/2] docs: improve library document and README --- README.md | 11 +++-------- src/code.rs | 41 +++++++++++++++++++++++++++-------------- src/config.rs | 12 ++++++------ src/easy.rs | 12 ++++++++---- src/lib.rs | 9 --------- src/nonce.rs | 14 ++++++-------- 6 files changed, 50 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 36ac79a..0ae1fe7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ ![GitHub License](https://img.shields.io/github/license/nakaryo716/tiny_google_oidc) Tiny library for Google's OpenID Connect. -Implementation in server flow +This library provides essential tools for handling Google's OpenID Connect flow, including +generating authentication URLs, verifying tokens, and managing access/refresh tokens. +Implementation in server flow. [google document](https://developers.google.com/identity/openid-connect/openid-connect) ## Feature - Generate a CSRF Token @@ -13,13 +15,6 @@ Implementation in server flow - Verify CSRF token and retrieve id_token - Exchange code for id_token (using reqwest) - Decode id_token (Base64URLDecode) to get user information -- Refresh access token using refresh token (using reqwest) -- Revoke access/refresh token (using reqwest) -## Caution -This library is designed for direct communication with Google over HTTPS. -It does not validate the id_token when converting it to a JWT, -so the id_token cannot be passed to other components of your app. -[See document](https://developers.google.com/identity/openid-connect/openid-connect#obtainuserinfo) ## Example Here's hou you can use this library with the axum framework. ### 1. Create Config diff --git a/src/code.rs b/src/code.rs index 50093a1..d2754dc 100644 --- a/src/code.rs +++ b/src/code.rs @@ -21,7 +21,8 @@ //! //! # Examples //! ## Generating an Authorization Request URL -//! ```rust,no_run +//! ```rust +//! # use tiny_google_oidc::{config::Config, csrf_token::CSRFToken, nonce::Nonce, code::{CodeRequest, AdditionalScope, AccessType}}; //! let config = Config::builder() //! .client_id("your_client_id") //! .redirect_uri("your_redirect_uri") @@ -29,18 +30,23 @@ //! //! let csrf_token = CSRFToken::new().unwrap(); //! let nonce = Nonce::new(); +//! let access_type = AccessType::Online; //! let scope = AdditionalScope::Email; //! -//! let request = CodeRequest::new(true, &config, scope, &csrf_token, &nonce); +//! let request = CodeRequest::new(access_type, &config, scope, &csrf_token, &nonce); //! let url = request.try_into_url().unwrap(); //! println!("Auth URL: {}", url); //! ``` //! //! ## Handling the Callback and Verifying the Authorization Code -//! ```rust,no_run +//! ```rust +//! # use tiny_google_oidc::{code::RawCodeResponse, csrf_token::CSRFToken}; +//! # use std::collections::HashMap; +//! # let store: HashMap = HashMap::default(); +//! # let req: http::Request<_>; //! let response = RawCodeResponse::new(req).unwrap(); //! // get stored CSRF token From DB(Redis, in memory ...) -//! let csrf_token = store.get("csrf_token_key")?; +//! let csrf_token = store.get("csrf_token_key".into())?; //! //! let code = response.exchange_with_code(csrf_token).expect("CSRF token mismatch!"); //! ``` @@ -70,7 +76,8 @@ use std::{collections::HashMap, iter::Iterator}; /// /// # Purpose /// The `Code` is used to construct an `IDTokenRequest`, which is required to retrieve an ID token from Google. -/// ```rust,no_run +/// ```rust +/// # use tiny_google_oidc::{code::Code, id_token::IDTokenRequest}; /// let code: Code = Code::new_with_verify_csrf(res, stored_csrf_token)?; /// let id_token_req = IDTokenRequest::new(&config, code); /// ``` @@ -80,9 +87,12 @@ use std::{collections::HashMap, iter::Iterator}; /// Use either `Code::new_with_verify_csrf` or `RawCodeResponse::exchange_with_code` to validate and create a `Code`. /// /// # Example -/// ```rust,no_run +/// ```rust +/// # use tiny_google_oidc::{csrf_token::CSRFToken, code::RawCodeResponse}; +/// # use std::collections::HashMap; +/// # let store: HashMap = HashMap::default(); /// let response = RawCodeResponse::new(req).unwrap(); -/// let csrf_token = store.get("csrf_token_key")?; +/// let csrf_token = store.get("csrf_token_key".into())?; /// /// let code = response.exchange_with_code(csrf_token).expect("CSRF token mismatch!"); /// ``` @@ -107,7 +117,8 @@ impl Code { /// Generates a URL to initiate the authorization request. /// # Example -/// ```rust,no_run +/// ```rust +/// # use tiny_google_oidc::{config::Config, csrf_token::CSRFToken, nonce::Nonce, code::{AdditionalScope, CodeRequest}; /// let config = Config::builder() /// .client_id("your_client_id") /// .redirect_uri("your_redirect_uri") @@ -204,7 +215,11 @@ impl<'a> CodeRequest<'a> { /// A response from Google containing an unverified authorization code and state. /// Must be validated using a CSRF token before use. /// # Example -/// ```rust,no_run +/// ```rust +/// # use tiny_google_oidc::{code::RawCodeResponse, csrf_token::CSRFToken}; +/// # use std::collections::HashMap; +/// # let req: http::Request<_>; +/// let store: HashMap = HashMap::default(); /// let response = RawCodeResponse::new(req).unwrap(); /// let csrf_token = store.get("csrf_token_key")?; /// @@ -307,11 +322,9 @@ pub enum AccessType { /// No additional scopes are added /// /// # Example -/// ```rust,no_run -/// use crate::code::AdditionalScope; -/// -/// let additional_scopes = AdditionalScope::Both; -/// let request = CodeRequest::new(true, &config, additional_scopes, &csrf_token, &nonce); +/// ```rust +/// # use tiny_google_oidc::code::{AdditionalScope, AccessType, CodeRequest}; +/// let request = CodeRequest::new(AccessType::Online, &config, AdditionalScope::Both, &csrf_token, &nonce); /// let url = request.into_url().unwrap(); /// println!("Authorization URL: {}", url); /// ``` diff --git a/src/config.rs b/src/config.rs index 2ca99dd..c451b63 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,7 @@ //! //! # Example //! ```rust -//! use tny_google_oidc::config::Config; +//! # use tiny_google_oidc::config::Config; //! //! let config = Config::builder() //! .auth_endpoint("https://accounts.google.com/o/oauth2/auth") @@ -172,7 +172,7 @@ impl ConfigBuilder { /// use tiny_google_oidc::config::AuthEndPoint; /// let endpoint_str = "https://accounts.google.com/o/oauth2/auth"; /// let auth_endpoint: AuthEndPoint = endpoint_str.into(); -/// assert_eq!(auth_endpoint, AuthEndPoint(endpoint_str.to_string())) +/// assert_eq!(auth_endpoint.value(), endpoint_str) /// ``` #[derive(Debug, Clone, Default, PartialEq)] pub struct AuthEndPoint(pub(crate) String); @@ -215,7 +215,7 @@ impl From for AuthEndPoint { /// /// let client_id_str = "your-client-id"; /// let client_id: ClientID = client_id_str.into(); -/// assert_eq!(client_id, ClientID(client_id_str.to_string())); +/// assert_eq!(client_id.value(), client_id_str); /// ``` #[derive(Debug, Clone, Default, PartialEq)] pub struct ClientID(pub(crate) String); @@ -258,7 +258,7 @@ impl From for ClientID { /// /// let secret_str = "your-client-secret"; /// let client_secret: ClientSecret = secret_str.into(); -/// assert_eq!(client_secret, ClientSecret(secret_str.to_string())); +/// assert_eq!(client_secret.value(), secret_str); /// ``` #[derive(Debug, Clone, Default, PartialEq)] pub struct ClientSecret(pub(crate) String); @@ -300,7 +300,7 @@ impl From for ClientSecret { /// /// let token_endpoint_str = "https://oauth2.googleapis.com/token"; /// let token_endpoint: TokenEndPoint = token_endpoint_str.into(); -/// assert_eq!(token_endpoint, TokenEndPoint(token_endpoint_str.to_string())); +/// assert_eq!(token_endpoint.value(), token_endpoint_str); /// ``` #[derive(Debug, Clone, Default, PartialEq)] pub struct TokenEndPoint(pub(crate) String); @@ -343,7 +343,7 @@ impl From for TokenEndPoint { /// /// let redirect_uri_str = "https://your-app.com/callback"; /// let redirect_uri: RedirectURI = redirect_uri_str.into(); -/// assert_eq!(redirect_uri, RedirectURI(redirect_uri_str.to_string())); +/// assert_eq!(redirect_uri.value(), redirect_uri_str); /// ``` #[derive(Debug, Clone, Default, PartialEq)] pub struct RedirectURI(pub(crate) String); diff --git a/src/easy.rs b/src/easy.rs index b6d4dbe..5fe38b7 100644 --- a/src/easy.rs +++ b/src/easy.rs @@ -47,7 +47,9 @@ use crate::{ /// Returns an error if token generation or URI construction fails. /// /// # Examples -/// ```no_run +/// ```rust +/// # use tiny_google_oidc::{config::ConfigBuilder, code::{AdditionalScope, AccessType}, easy::generate_auth_redirect}; +/// # let config = ConfigBuilder::new().build(); /// let (csrf_token, nonce, uri) = generate_auth_redirect( /// &config, /// AccessType::Offline, @@ -88,15 +90,17 @@ pub fn generate_auth_redirect( /// Returns an error if CSRF validation fails or if the query cannot be parsed. /// /// # Examples -/// ```no_run -/// fn callback_handler(config: &Config, stored_csrf_token: &str, req: Request) -> Result<(), Error> { +/// ```rust +/// # use tiny_google_oidc::{easy::create_id_token_request, config::Config}; +/// # fn callback_handler(config: &Config, stored_csrf_token: &str, req: http::Request) -> Result<(), Box> { /// // http::Request is implemented QueryExtractor trait /// let id_token_req = create_id_token_request( /// config, /// stored_csrf_token, /// req /// )?; -/// } +/// # Ok(()) +/// # } /// ``` pub fn create_id_token_request<'a, Q: QueryExtractor>( config: &'a Config, diff --git a/src/lib.rs b/src/lib.rs index 98f535b..fb3394f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,15 +10,6 @@ //! - Verify CSRF token and retrieve id_token //! - Exchange code for id_token (using reqwest) //! - Decode id_token (Base64URLDecode) to get user information -//! - Refresh access token using refresh token (using reqwest) -//! - Revoke access/refresh token (using reqwest) -//! # Caution -//! - This library is designed for direct communication with Google over HTTPS. -//! - It does **not** validate the `id_token` when converting it to a JWT. As a result, the `id_token` -//! should not be passed to other components of your application. -//! - For more details, refer to the -//! -//! [Google OpenID Connect documentation](https://developers.google.com/identity/openid-connect/openid-connect#obtainuserinfo). //! # Examples //! For example usage, see the [examples directory](https://github.com/nakaryo716/tiny_google_oidc.git). pub mod code; diff --git a/src/nonce.rs b/src/nonce.rs index c9cb123..b53613b 100644 --- a/src/nonce.rs +++ b/src/nonce.rs @@ -6,30 +6,28 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Nonce(pub(crate) String); -/// # **Overview** +/// # Overview /// A `Nonce` is a **unique, random value** used to prevent replay attacks in OpenID Connect authentication. /// It ensures that the authentication request and response belong to the same session. /// /// This structure automatically generates a new random nonce using `UUIDv4` when created. /// -/// # **Usage** -/// +/// # Usage /// - The nonce is included in the authentication request (`CodeRequest`). /// - When receiving an ID token from Google, the `Nonce` in the token should be verified /// against the original nonce sent in the request. /// -/// # **Implementation Details** -/// +/// # Implementation Details /// - The `Nonce` value is a **UUIDv4 string**. /// - It implements `Serialize` and `Deserialize` for easy integration with JSON-based flows. /// -/// # **Example** +/// # Example /// /// ```rust,no_run -/// use crate::nonce::Nonce; +/// use tiny_google_oidc::nonce::Nonce; /// /// let nonce = Nonce::new(); -/// println!("Generated Nonce: {}", nonce.0); +/// println!("Generated Nonce: {:?}", nonce); /// ``` /// - Always **verify the nonce** in the received ID token against the originally generated value /// to ensure security. From e921a9a23a283eb2b8e6c8000f54f12856838662 Mon Sep 17 00:00:00 2001 From: Ryo Nakaya Date: Sun, 14 Dec 2025 16:49:44 +0900 Subject: [PATCH 2/2] docs: fix library example code for passing test --- README.md | 2 +- src/code.rs | 59 ++++------------------------------------------------- src/easy.rs | 16 ++++++++------- src/lib.rs | 2 +- 4 files changed, 15 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 0ae1fe7..cfa05da 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Tiny library for Google's OpenID Connect. This library provides essential tools for handling Google's OpenID Connect flow, including -generating authentication URLs, verifying tokens, and managing access/refresh tokens. +generating authentication URLs, verifying tokens. Implementation in server flow. [google document](https://developers.google.com/identity/openid-connect/openid-connect) ## Feature diff --git a/src/code.rs b/src/code.rs index d2754dc..17cfebc 100644 --- a/src/code.rs +++ b/src/code.rs @@ -20,8 +20,8 @@ //! - This is obtained after validating the `RawCodeResponse` with a CSRF token. //! //! # Examples -//! ## Generating an Authorization Request URL -//! ```rust +//! Generating an Authorization Request URL +//! ```rust, no_run //! # use tiny_google_oidc::{config::Config, csrf_token::CSRFToken, nonce::Nonce, code::{CodeRequest, AdditionalScope, AccessType}}; //! let config = Config::builder() //! .client_id("your_client_id") @@ -38,19 +38,6 @@ //! println!("Auth URL: {}", url); //! ``` //! -//! ## Handling the Callback and Verifying the Authorization Code -//! ```rust -//! # use tiny_google_oidc::{code::RawCodeResponse, csrf_token::CSRFToken}; -//! # use std::collections::HashMap; -//! # let store: HashMap = HashMap::default(); -//! # let req: http::Request<_>; -//! let response = RawCodeResponse::new(req).unwrap(); -//! // get stored CSRF token From DB(Redis, in memory ...) -//! let csrf_token = store.get("csrf_token_key".into())?; -//! -//! let code = response.exchange_with_code(csrf_token).expect("CSRF token mismatch!"); -//! ``` -//! //! # Flow //! 1. Generate a CSRF token (`CSRFToken`) and include it in the authorization request. //! 2. Redirect the user to Google's authentication page. @@ -76,27 +63,10 @@ use std::{collections::HashMap, iter::Iterator}; /// /// # Purpose /// The `Code` is used to construct an `IDTokenRequest`, which is required to retrieve an ID token from Google. -/// ```rust -/// # use tiny_google_oidc::{code::Code, id_token::IDTokenRequest}; -/// let code: Code = Code::new_with_verify_csrf(res, stored_csrf_token)?; -/// let id_token_req = IDTokenRequest::new(&config, code); -/// ``` -/// /// # How to Create /// A `Code` can only be created after validating the `CSRFToken`. /// Use either `Code::new_with_verify_csrf` or `RawCodeResponse::exchange_with_code` to validate and create a `Code`. /// -/// # Example -/// ```rust -/// # use tiny_google_oidc::{csrf_token::CSRFToken, code::RawCodeResponse}; -/// # use std::collections::HashMap; -/// # let store: HashMap = HashMap::default(); -/// let response = RawCodeResponse::new(req).unwrap(); -/// let csrf_token = store.get("csrf_token_key".into())?; -/// -/// let code = response.exchange_with_code(csrf_token).expect("CSRF token mismatch!"); -/// ``` -/// /// # Notes /// - Always validate the `CSRFToken` before using the `Code`. /// - The `Code` is essential for constructing an `IDTokenRequest` to retrieve an ID token. @@ -117,8 +87,8 @@ impl Code { /// Generates a URL to initiate the authorization request. /// # Example -/// ```rust -/// # use tiny_google_oidc::{config::Config, csrf_token::CSRFToken, nonce::Nonce, code::{AdditionalScope, CodeRequest}; +/// ```rust, no_run +/// # use tiny_google_oidc::{config::Config, csrf_token::CSRFToken, nonce::Nonce, code::{AdditionalScope, AccessType, CodeRequest}}; /// let config = Config::builder() /// .client_id("your_client_id") /// .redirect_uri("your_redirect_uri") @@ -130,7 +100,6 @@ impl Code { /// /// let request = CodeRequest::new(AccessType::Online, &config, scope, &csrf_token, &nonce); /// let url = request.try_into_url().unwrap(); -/// println!("Auth URL: {}", url); /// ``` #[derive(Debug, Clone)] pub struct CodeRequest<'a> { @@ -214,17 +183,6 @@ impl<'a> CodeRequest<'a> { /// A response from Google containing an unverified authorization code and state. /// Must be validated using a CSRF token before use. -/// # Example -/// ```rust -/// # use tiny_google_oidc::{code::RawCodeResponse, csrf_token::CSRFToken}; -/// # use std::collections::HashMap; -/// # let req: http::Request<_>; -/// let store: HashMap = HashMap::default(); -/// let response = RawCodeResponse::new(req).unwrap(); -/// let csrf_token = store.get("csrf_token_key")?; -/// -/// let code = response.exchange_with_code(csrf_token).expect("CSRF token mismatch!"); -/// ``` #[derive(Debug, Clone)] pub struct RawCodeResponse { state: RawCSRFToken, @@ -320,15 +278,6 @@ pub enum AccessType { /// /// ## None /// No additional scopes are added -/// -/// # Example -/// ```rust -/// # use tiny_google_oidc::code::{AdditionalScope, AccessType, CodeRequest}; -/// let request = CodeRequest::new(AccessType::Online, &config, AdditionalScope::Both, &csrf_token, &nonce); -/// let url = request.into_url().unwrap(); -/// println!("Authorization URL: {}", url); -/// ``` -/// /// If **no additional scopes** are specified, the request will **only include `openid`**, which is required for authentication. /// /// # Notes diff --git a/src/easy.rs b/src/easy.rs index 5fe38b7..40a74da 100644 --- a/src/easy.rs +++ b/src/easy.rs @@ -48,13 +48,15 @@ use crate::{ /// /// # Examples /// ```rust -/// # use tiny_google_oidc::{config::ConfigBuilder, code::{AdditionalScope, AccessType}, easy::generate_auth_redirect}; -/// # let config = ConfigBuilder::new().build(); -/// let (csrf_token, nonce, uri) = generate_auth_redirect( -/// &config, -/// AccessType::Offline, -/// AdditionalScope::Both -/// )?; +/// # use tiny_google_oidc::{config::Config, code::{AdditionalScope, AccessType}, easy::generate_auth_redirect}; +/// # fn doc(config: Config) -> Result<(), Box> { +/// let (csrf_token, nonce, uri) = generate_auth_redirect( +/// &config, +/// AccessType::Offline, +/// AdditionalScope::Both +/// )?; +/// Ok(()) +/// # } /// ``` pub fn generate_auth_redirect( config: &Config, diff --git a/src/lib.rs b/src/lib.rs index fb3394f..e5493b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! Tiny library for Google's OpenID Connect. //! //! This library provides essential tools for handling Google's OpenID Connect flow, including -//! generating authentication URLs, verifying tokens, and managing access/refresh tokens. +//! generating authentication URLs, verifying tokens. //! Implementation in server flow. //! [google document](https://developers.google.com/identity/openid-connect/openid-connect) //! # Feature