use azure_core::{error::ErrorKind, Error}; use azure_identity::{device_code_flow, refresh_token}; use azure_storage::prelude::*; use azure_storage_blobs::prelude::*; use futures::stream::StreamExt; use std::env::{args, var}; #[tokio::main] async fn main() -> azure_core::Result<()> { let client_id = var("CLIENT_ID").expect("Missing CLIENT_ID environment variable."); let tenant_id = var("TENANT_ID").expect("Missing TENANT_ID environment variable."); let storage_account_name = args() .nth(1) .expect("please specify the storage account name as first command line parameter"); let http_client = azure_core::new_http_client(); // the process requires two steps. The first is to ask for // the code to show to the user. This is done with the following // function. Notice you can pass as many scopes as you want. // Since we are asking for the "offline_access" scope we will // receive the refresh token as well. // We are requesting access to the storage account passed as parameter. let device_code_flow = device_code_flow::start( http_client.clone(), &tenant_id, &client_id, &[ &format!("https://{storage_account_name}.blob.core.windows.net/user_impersonation"), "offline_access", ], ) .await?; // now we must show the user the authentication message. It // will point the user to the login page and show the code // they have to specify. println!("{}", device_code_flow.message()); // now we poll the auth endpoint until the user // completes the authentication. The following stream can // return, besides errors, a success meaning either // Success or Pending. The loop will continue until we // get either a Success or an error. let mut stream = device_code_flow.stream(); let authorization = loop { match stream.next().await { Some(Ok(authorization)) => break authorization, Some(Err(_)) => continue, None => { return Err(Error::with_message(ErrorKind::Credential, || { "device flow stream ended unexpectedly" })) } } }; // we can now spend the access token in other crates. In this example we are // creating an Azure Storage client using the access token. let storage_credentials = StorageCredentials::bearer_token(authorization.access_token().clone()); let blob_service_client = BlobServiceClient::new(storage_account_name, storage_credentials); // now we enumerate the containers in the specified storage account. let containers = blob_service_client .list_containers() .into_stream() .next() .await .expect("stream failed")?; println!("\nList containers completed succesfully: {containers:?}"); // If we want to use the refresh token to get a new access token (such as if // we wanted to bump the expiry window on the token), we can do the // following if let Some(refresh_token) = authorization.refresh_token() { let refreshed_token = refresh_token::exchange(http_client, &tenant_id, &client_id, None, refresh_token) .await?; println!("refreshed token == {refreshed_token:#?}"); } Ok(()) }