//! //! This example showcases the Github OAuth2 process for requesting access to the user's public repos and //! email address. //! //! Before running it, you'll need to generate your own Github OAuth2 credentials. //! //! In order to run the example call: //! //! ```sh //! GITHUB_CLIENT_ID=xxx GITHUB_CLIENT_SECRET=yyy cargo run --example github //! ``` //! //! ...and follow the instructions. //! use oauth2::basic::BasicClient; use oauth2::reqwest; use oauth2::{ AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, RedirectUrl, Scope, TokenResponse, TokenUrl, }; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::TcpListener; use url::Url; use std::env; #[tokio::main] async fn main() { let github_client_id = ClientId::new( env::var("GITHUB_CLIENT_ID").expect("Missing the GITHUB_CLIENT_ID environment variable."), ); let github_client_secret = ClientSecret::new( env::var("GITHUB_CLIENT_SECRET") .expect("Missing the GITHUB_CLIENT_SECRET environment variable."), ); let auth_url = AuthUrl::new("https://github.com/login/oauth/authorize".to_string()) .expect("Invalid authorization endpoint URL"); let token_url = TokenUrl::new("https://github.com/login/oauth/access_token".to_string()) .expect("Invalid token endpoint URL"); // Set up the config for the Github OAuth2 process. let client = BasicClient::new(github_client_id) .set_client_secret(github_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"), ); let http_client = reqwest::ClientBuilder::new() // Following redirects opens the client up to SSRF vulnerabilities. .redirect(reqwest::redirect::Policy::none()) .build() .expect("Client should build"); // 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 user's public repos and email. .add_scope(Scope::new("public_repo".to_string())) .add_scope(Scope::new("user:email".to_string())) .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").await.unwrap(); loop { if let Ok((mut stream, _)) = listener.accept().await { let mut reader = BufReader::new(&mut stream); let mut request_line = String::new(); reader.read_line(&mut request_line).await.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()).await.unwrap(); // The server will terminate itself after collecting the first code. break (code, state); } } }; println!("Github returned the following code:\n{}\n", code.secret()); println!( "Github returned the following state:\n{} (expected `{}`)\n", state.secret(), csrf_state.secret() ); // Exchange the code with a token. let token_res = client.exchange_code(code).request_async(&http_client).await; println!("Github returned the following token:\n{token_res:?}\n"); if let Ok(token) = token_res { // NB: Github returns a single comma-separated "scope" parameter instead of multiple // space-separated scopes. Github-specific clients can parse this scope into // multiple scopes by splitting at the commas. Note that it's not safe for the // library to do this by default because RFC 6749 allows scopes to contain commas. let scopes = if let Some(scopes_vec) = token.scopes() { scopes_vec .iter() .flat_map(|comma_separated| comma_separated.split(',')) .collect::>() } else { Vec::new() }; println!("Github returned the following scopes:\n{scopes:?}\n"); } }