github_async.rs 5.5 KB

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