-
Notifications
You must be signed in to change notification settings - Fork 0
/
sign_in_with_microsoft_oauth_credential_on_auth_code.rs
196 lines (169 loc) · 5.28 KB
/
sign_in_with_microsoft_oauth_credential_on_auth_code.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! An example to sign in with Microsoft OAuth credential by session-based interface
//! on the Authorization Code grant type of the OAuth 2.0.
//!
//! ```shell
//! $ cargo run --example sign_in_with_microsoft_oauth_credential_on_auth_code --features oauth
//! ```
#![cfg(feature = "oauth")]
use std::collections::HashSet;
use std::sync::Arc;
use axum::extract::{Query, State};
use axum::{routing::get, Router};
use serde::Deserialize;
use tokio::sync::{mpsc, Mutex};
use fars::oauth::AuthorizationCodeSession;
use fars::oauth::ClientId;
use fars::oauth::CsrfState;
use fars::oauth::MicrosoftAuthorizationCodeClient;
use fars::oauth::OAuthScope;
use fars::oauth::RedirectUrl;
use fars::oauth::{AuthorizationCode, MicrosoftIssuer};
use fars::ApiKey;
use fars::Config;
use fars::OAuthRequestUri;
use fars::ProviderId;
#[derive(Clone)]
struct ServerState {
config: Arc<Mutex<Config>>,
oauth_session: Arc<Mutex<AuthorizationCodeSession>>,
tx: mpsc::Sender<()>,
}
#[allow(dead_code)]
#[derive(Deserialize)]
struct QueryParameters {
code: Option<String>,
scope: Option<String>,
state: Option<String>,
error: Option<String>,
}
async fn handle_redirect(
state: State<ServerState>,
Query(params): Query<QueryParameters>,
) -> String {
// Check query parameters.
if let Some(error) = params.error {
eprintln!("Error: {}", error,);
return "Error".to_string();
}
let auth_code;
if let Some(code) = params.code {
auth_code = code;
} else {
eprintln!("Error: No authorization code.");
return "Error".to_string();
}
let auth_state;
if let Some(param_state) = params.state {
auth_state = param_state;
} else {
eprintln!("Error: No state.");
return "Error".to_string();
}
// Continue to sign in process.
match continue_sign_in(state, auth_code, auth_state).await {
| Ok(_) => {
"Succeeded to sign in with Microsoft OAuth credential.".to_string()
},
| Err(e) => {
eprintln!("Error: {:?}", e);
"Error".to_string()
},
}
}
async fn continue_sign_in(
state: State<ServerState>,
auth_code: String,
auth_state: String,
) -> anyhow::Result<()> {
let oauth_session = state
.oauth_session
.lock()
.await;
let sender = state.tx.clone();
// Exchange authorization code into OAuth token.
let token = oauth_session
.exchange_code_into_token(
AuthorizationCode::new(auth_code),
CsrfState::new(auth_state),
)
.await
.map_err(|e| {
// Stop server
tokio::spawn(async move {
sender.send(()).await.unwrap();
});
anyhow::anyhow!("{:?}", e)
})?;
let config = state.config.lock().await;
let sender = state.tx.clone();
// Get a session by signing in Microsoft OAuth credential.
let session = config
.sign_in_with_oauth_credential(
OAuthRequestUri::new("http://localhost:8080"),
token.create_idp_post_body(ProviderId::Microsoft)?,
)
.await
.map_err(|e| {
// Stop server
tokio::spawn(async move {
sender.send(()).await.unwrap();
});
anyhow::anyhow!("{:?}", e)
})?;
println!(
"Succeeded to sign in with Facebook OAuth credential: {:?}",
session
);
// Stop server
let sender = state.tx.clone();
sender.send(()).await.unwrap();
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Get Client ID from the environment variables.
let client_id = ClientId::from_env("MICROSOFT_CLIENT_ID")?;
// Create an OAuth client.
let oauth_client = MicrosoftAuthorizationCodeClient::new(
client_id,
None,
RedirectUrl::new("http://localhost:8080/auth/microsoft-callback")?,
MicrosoftIssuer::Consumers,
)?;
// Generate an OAuth session with authorization URL.
let session = oauth_client.generate_authorization_session(HashSet::from([
OAuthScope::open_id(),
OAuthScope::open_id_email(),
]));
// Open the authorization URL in the default browser.
webbrowser::open(session.authorize_url.inner())?;
// Create a channel to receive a signal to stop the server.
let (tx, mut rx) = mpsc::channel::<()>(1);
// Create a server state.
let server_state = ServerState {
config: Arc::new(Mutex::new(Config::new(
ApiKey::from_env()?,
))),
oauth_session: Arc::new(Mutex::new(session)),
tx,
};
// Build application with redirection handler.
let app = Router::new()
.route(
"/auth/microsoft-callback",
get(handle_redirect),
)
.with_state(server_state);
// Run it with hyper on localhost:8080 to receive the authorization code by redirection.
let listener = tokio::net::TcpListener::bind("localhost:8080").await?;
// Wait for the server to stop or receive a signal to stop the server.
tokio::select! {
| _ = rx.recv() => {
println!("Received a signal to stop the server.");
},
| _ = async { axum::serve(listener, app).await } => {
println!("Server stopped.");
},
}
Ok(())
}