Skip to content

Commit 5b53fc7

Browse files
committed
Add an example for device code oidc login
1 parent b266088 commit 5b53fc7

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/device-code/Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "example-device-code-login"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
license = "Apache-2.0"
7+
8+
[[bin]]
9+
name = "example-device-code-login"
10+
test = false
11+
12+
[dependencies]
13+
anyhow = { workspace = true }
14+
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
15+
clap = { version = "4.0.15", features = ["derive"] }
16+
tracing-subscriber = { workspace = true }
17+
url = "2.3.1"
18+
dirs = "5.0.1"
19+
20+
[dependencies.matrix-sdk]
21+
# when copy-pasting this, please use a git dependency or make sure that you
22+
# have copied the example as it was at the time of the release you use.
23+
path = "../../crates/matrix-sdk"
24+
features = ["experimental-oidc"]
25+
26+
[package.metadata.release]
27+
release = false

examples/device-code/src/main.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use std::collections::HashMap;
2+
3+
use anyhow::Result;
4+
use clap::Parser;
5+
use matrix_sdk::{
6+
oidc::{
7+
registrations::OidcRegistrations,
8+
types::{
9+
iana::oauth::OAuthClientAuthenticationMethod,
10+
oidc::ApplicationType,
11+
registration::{ClientMetadata, Localized, VerifiedClientMetadata},
12+
requests::GrantType,
13+
scope::ScopeToken,
14+
},
15+
},
16+
Client,
17+
};
18+
use url::Url;
19+
20+
/// A command line example showcasing how to login using a device code.
21+
///
22+
/// Another device, will verify the device code.
23+
#[derive(Parser, Debug)]
24+
struct Cli {
25+
/// Set the homeserver that should be used for authentication.
26+
#[clap(long, required = true)]
27+
homeserver: Url,
28+
29+
/// Add extra scopes to the request.
30+
#[clap(long)]
31+
custom_scopes: Option<Vec<ScopeToken>>,
32+
33+
/// Enable verbose logging output.
34+
#[clap(short, long, action)]
35+
verbose: bool,
36+
}
37+
38+
/// Generate the OIDC client metadata.
39+
///
40+
/// For simplicity, we use most of the default values here, but usually this
41+
/// should be adapted to the provider metadata to make interactions as secure as
42+
/// possible, for example by using the most secure signing algorithms supported
43+
/// by the provider.
44+
fn client_metadata() -> VerifiedClientMetadata {
45+
let client_uri = Url::parse("https://github.com/matrix-org/matrix-rust-sdk")
46+
.expect("Couldn't parse client URI");
47+
48+
ClientMetadata {
49+
// This is a native application (in contrast to a web application, that runs in a browser).
50+
application_type: Some(ApplicationType::Native),
51+
// Native clients should be able to register the loopback interface and then point to any
52+
// port when needing a redirect URI. An alternative is to use a custom URI scheme registered
53+
// with the OS.
54+
redirect_uris: None,
55+
// We are going to use the Authorization Code flow, and of course we want to be able to
56+
// refresh our access token.
57+
grant_types: Some(vec![GrantType::RefreshToken, GrantType::DeviceCode]),
58+
// A native client shouldn't use authentication as the credentials could be intercepted.
59+
// Other protections are in place for the different requests.
60+
token_endpoint_auth_method: Some(OAuthClientAuthenticationMethod::None),
61+
// The following fields should be displayed in the OIDC provider interface as part of the
62+
// process to get the user's consent. It means that these should contain real data so the
63+
// user can make sure that they allow the proper application.
64+
// We are cheating here because this is an example.
65+
client_name: Some(Localized::new("matrix-rust-sdk-device-code-login".to_owned(), [])),
66+
contacts: Some(vec!["[email protected]".to_owned()]),
67+
client_uri: Some(Localized::new(client_uri.clone(), [])),
68+
policy_uri: Some(Localized::new(client_uri.clone(), [])),
69+
tos_uri: Some(Localized::new(client_uri, [])),
70+
..Default::default()
71+
}
72+
.validate()
73+
.unwrap()
74+
}
75+
76+
#[tokio::main]
77+
async fn main() -> Result<()> {
78+
let cli = Cli::parse();
79+
80+
if cli.verbose {
81+
tracing_subscriber::fmt::init();
82+
}
83+
84+
let server_name = cli.homeserver;
85+
let client = Client::builder().server_name_or_homeserver_url(server_name).build().await?;
86+
87+
let metadata = client_metadata();
88+
89+
let data_dir = dirs::data_dir().expect("no data_dir directory found");
90+
let registrations_file = data_dir.join("matrix_sdk/oidc").join("registrations.json");
91+
92+
let static_registrations = HashMap::new();
93+
94+
let registrations =
95+
OidcRegistrations::new(&registrations_file, client_metadata(), static_registrations)?;
96+
97+
let oidc = client.oidc();
98+
99+
let mut login_device_code = oidc.login_with_device_code(metadata, registrations);
100+
101+
let auth_grant_response = login_device_code.device_code_for_login(cli.custom_scopes).await?;
102+
103+
println!(
104+
"Log in using this {}",
105+
auth_grant_response.verification_uri_complete().unwrap().clone().into_secret()
106+
);
107+
println!(
108+
"You can also go to {} and type in the code {}",
109+
auth_grant_response.verification_uri(),
110+
auth_grant_response.user_code().clone().into_secret()
111+
);
112+
113+
login_device_code.wait_finish_login().await?;
114+
115+
let user_id = client.user_id().unwrap();
116+
117+
println!("Successfully logged in as {user_id} using the device code");
118+
119+
Ok(())
120+
}

0 commit comments

Comments
 (0)