//! //! 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 url::Url; use std::env; use std::io::{BufRead, BufReader, Write}; use std::net::TcpListener; 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::blocking::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").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!("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(&http_client); 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"); } }