1 //!
2 //! This example showcases the Github OAuth2 process for requesting access to the user's public repos and
3 //! email address.
4 //!
5 //! Before running it, you'll need to generate your own Github OAuth2 credentials.
6 //!
7 //! In order to run the example call:
8 //!
9 //! ```sh
10 //! GITHUB_CLIENT_ID=xxx GITHUB_CLIENT_SECRET=yyy cargo run --example github
11 //! ```
12 //!
13 //! ...and follow the instructions.
14 //!
15 
16 use oauth2::basic::BasicClient;
17 
18 // Alternatively, this can be `oauth2::curl::http_client` or a custom client.
19 use oauth2::reqwest::http_client;
20 use oauth2::{
21     AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, RedirectUrl, Scope,
22     TokenResponse, TokenUrl,
23 };
24 use std::env;
25 use std::io::{BufRead, BufReader, Write};
26 use std::net::TcpListener;
27 use url::Url;
28 
main()29 fn main() {
30     let github_client_id = ClientId::new(
31         env::var("GITHUB_CLIENT_ID").expect("Missing the GITHUB_CLIENT_ID environment variable."),
32     );
33     let github_client_secret = ClientSecret::new(
34         env::var("GITHUB_CLIENT_SECRET")
35             .expect("Missing the GITHUB_CLIENT_SECRET environment variable."),
36     );
37     let auth_url = AuthUrl::new("https://github.com/login/oauth/authorize".to_string())
38         .expect("Invalid authorization endpoint URL");
39     let token_url = TokenUrl::new("https://github.com/login/oauth/access_token".to_string())
40         .expect("Invalid token endpoint URL");
41 
42     // Set up the config for the Github OAuth2 process.
43     let client = BasicClient::new(
44         github_client_id,
45         Some(github_client_secret),
46         auth_url,
47         Some(token_url),
48     )
49     // This example will be running its own server at localhost:8080.
50     // See below for the server implementation.
51     .set_redirect_uri(
52         RedirectUrl::new("http://localhost:8080".to_string()).expect("Invalid redirect URL"),
53     );
54 
55     // Generate the authorization URL to which we'll redirect the user.
56     let (authorize_url, csrf_state) = client
57         .authorize_url(CsrfToken::new_random)
58         // This example is requesting access to the user's public repos and email.
59         .add_scope(Scope::new("public_repo".to_string()))
60         .add_scope(Scope::new("user:email".to_string()))
61         .url();
62 
63     println!(
64         "Open this URL in your browser:\n{}\n",
65         authorize_url.to_string()
66     );
67 
68     // A very naive implementation of the redirect server.
69     let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
70     for stream in listener.incoming() {
71         if let Ok(mut stream) = stream {
72             let code;
73             let state;
74             {
75                 let mut reader = BufReader::new(&stream);
76 
77                 let mut request_line = String::new();
78                 reader.read_line(&mut request_line).unwrap();
79 
80                 let redirect_url = request_line.split_whitespace().nth(1).unwrap();
81                 let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
82 
83                 let code_pair = url
84                     .query_pairs()
85                     .find(|pair| {
86                         let &(ref key, _) = pair;
87                         key == "code"
88                     })
89                     .unwrap();
90 
91                 let (_, value) = code_pair;
92                 code = AuthorizationCode::new(value.into_owned());
93 
94                 let state_pair = url
95                     .query_pairs()
96                     .find(|pair| {
97                         let &(ref key, _) = pair;
98                         key == "state"
99                     })
100                     .unwrap();
101 
102                 let (_, value) = state_pair;
103                 state = CsrfToken::new(value.into_owned());
104             }
105 
106             let message = "Go back to your terminal :)";
107             let response = format!(
108                 "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
109                 message.len(),
110                 message
111             );
112             stream.write_all(response.as_bytes()).unwrap();
113 
114             println!("Github returned the following code:\n{}\n", code.secret());
115             println!(
116                 "Github returned the following state:\n{} (expected `{}`)\n",
117                 state.secret(),
118                 csrf_state.secret()
119             );
120 
121             // Exchange the code with a token.
122             let token_res = client.exchange_code(code).request(http_client);
123 
124             println!("Github returned the following token:\n{:?}\n", token_res);
125 
126             if let Ok(token) = token_res {
127                 // NB: Github returns a single comma-separated "scope" parameter instead of multiple
128                 // space-separated scopes. Github-specific clients can parse this scope into
129                 // multiple scopes by splitting at the commas. Note that it's not safe for the
130                 // library to do this by default because RFC 6749 allows scopes to contain commas.
131                 let scopes = if let Some(scopes_vec) = token.scopes() {
132                     scopes_vec
133                         .iter()
134                         .map(|comma_separated| comma_separated.split(','))
135                         .flatten()
136                         .collect::<Vec<_>>()
137                 } else {
138                     Vec::new()
139                 };
140                 println!("Github returned the following scopes:\n{:?}\n", scopes);
141             }
142 
143             // The server will terminate itself after collecting the first code.
144             break;
145         }
146     }
147 }
148