//! //! This example showcases the Google OAuth2 process for requesting access to the Google Calendar features //! and the user's profile. //! //! Before running it, you'll need to generate your own Google OAuth2 credentials. //! //! In order to run the example call: //! //! ```sh //! GOOGLE_CLIENT_ID=xxx GOOGLE_CLIENT_SECRET=yyy cargo run --example google //! ``` //! //! ...and follow the instructions. //! use oauth2::reqwest; use oauth2::{basic::BasicClient, StandardRevocableToken, TokenResponse}; use oauth2::{ AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl, RevocationUrl, Scope, TokenUrl, }; use url::Url; use std::env; use std::io::{BufRead, BufReader, Write}; use std::net::TcpListener; fn main() { let google_client_id = ClientId::new( env::var("GOOGLE_CLIENT_ID").expect("Missing the GOOGLE_CLIENT_ID environment variable."), ); let google_client_secret = ClientSecret::new( env::var("GOOGLE_CLIENT_SECRET") .expect("Missing the GOOGLE_CLIENT_SECRET environment variable."), ); let auth_url = AuthUrl::new("https://accounts.google.com/o/oauth2/v2/auth".to_string()) .expect("Invalid authorization endpoint URL"); let token_url = TokenUrl::new("https://www.googleapis.com/oauth2/v3/token".to_string()) .expect("Invalid token endpoint URL"); // Set up the config for the Google OAuth2 process. let client = BasicClient::new(google_client_id) .set_client_secret(google_client_secret) .set_auth_uri(auth_url) .set_token_uri(token_url) // This example will be running its own server at localhost:8080. // See below for the server implementation. .set_redirect_uri( RedirectUrl::new("http://localhost:8080".to_string()).expect("Invalid redirect URL"), ) // Google supports OAuth 2.0 Token Revocation (RFC-7009) .set_revocation_url( RevocationUrl::new("https://oauth2.googleapis.com/revoke".to_string()) .expect("Invalid revocation endpoint URL"), ); let http_client = reqwest::blocking::ClientBuilder::new() // Following redirects opens the client up to SSRF vulnerabilities. .redirect(reqwest::redirect::Policy::none()) .build() .expect("Client should build"); // Google supports Proof Key for Code Exchange (PKCE - https://oauth.net/2/pkce/). // Create a PKCE code verifier and SHA-256 encode it as a code challenge. let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256(); // Generate the authorization URL to which we'll redirect the user. let (authorize_url, csrf_state) = client .authorize_url(CsrfToken::new_random) // This example is requesting access to the "calendar" features and the user's profile. .add_scope(Scope::new( "https://www.googleapis.com/auth/calendar".to_string(), )) .add_scope(Scope::new( "https://www.googleapis.com/auth/plus.me".to_string(), )) .set_pkce_challenge(pkce_code_challenge) .url(); println!("Open this URL in your browser:\n{authorize_url}\n"); let (code, state) = { // A very naive implementation of the redirect server. let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); // The server will terminate itself after collecting the first code. let Some(mut stream) = listener.incoming().flatten().next() else { panic!("listener terminated without accepting a connection"); }; let mut reader = BufReader::new(&stream); let mut request_line = String::new(); reader.read_line(&mut request_line).unwrap(); let redirect_url = request_line.split_whitespace().nth(1).unwrap(); let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap(); let code = url .query_pairs() .find(|(key, _)| key == "code") .map(|(_, code)| AuthorizationCode::new(code.into_owned())) .unwrap(); let state = url .query_pairs() .find(|(key, _)| key == "state") .map(|(_, state)| CsrfToken::new(state.into_owned())) .unwrap(); let message = "Go back to your terminal :)"; let response = format!( "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}", message.len(), message ); stream.write_all(response.as_bytes()).unwrap(); (code, state) }; println!("Google returned the following code:\n{}\n", code.secret()); println!( "Google returned the following state:\n{} (expected `{}`)\n", state.secret(), csrf_state.secret() ); // Exchange the code with a token. let token_response = client .exchange_code(code) .set_pkce_verifier(pkce_code_verifier) .request(&http_client); println!("Google returned the following token:\n{token_response:?}\n"); // Revoke the obtained token let token_response = token_response.unwrap(); let token_to_revoke: StandardRevocableToken = match token_response.refresh_token() { Some(token) => token.into(), None => token_response.access_token().into(), }; client .revoke_token(token_to_revoke) .unwrap() .request(&http_client) .expect("Failed to revoke token"); }