Files
dioxus/examples/openid_connect_demo/src/views/header.rs
T
StygmatesandTruong Tan Dat b836851d02 Add openidconnect authentication demo (#1500)
* Add openidconnect authentication demo

* use_atom_ref usage to have a read/write handle on the atom

* Use default

* Code rewrite to better reflect the authentication flow

* Use the env macro instead of the build.rs to load env variables

* Add env variables

* Remove unnecessary dependency

* Add env variables to the root workspace

* Update readme

* Bump openidconnect version

* Use props to pass the client to the child components

* Code clean up

---------

Co-authored-by: Truong Tan Dat <truongt@igbmc.fr>
2023-10-20 12:43:24 -05:00

251 lines
11 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::{
oidc::{
authorize_url, email, exchange_refresh_token, init_oidc_client, log_out_url,
AuthRequestState, AuthTokenState, ClientState,
},
props::client::ClientProps,
router::Route,
storage::PersistentWrite,
FERMI_AUTH_REQUEST, FERMI_AUTH_TOKEN, FERMI_CLIENT,
};
use dioxus::prelude::*;
use dioxus_router::prelude::{Link, Outlet};
use fermi::*;
use openidconnect::{url::Url, OAuth2TokenResponse, TokenResponse};
#[component]
pub fn LogOut(cx: Scope<ClientProps>) -> Element {
let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
let fermi_auth_token_read = fermi_auth_token.read().clone();
let log_out_url_state = use_state(cx, || None::<Option<Result<Url, crate::errors::Error>>>);
cx.render(match fermi_auth_token_read {
Some(fermi_auth_token_read) => match fermi_auth_token_read.id_token.clone() {
Some(id_token) => match log_out_url_state.get() {
Some(log_out_url_result) => match log_out_url_result {
Some(uri) => match uri {
Ok(uri) => {
rsx! {
Link {
onclick: move |_| {
{
AuthTokenState::persistent_set(
fermi_auth_token,
Some(AuthTokenState::default()),
);
}
},
to: uri.to_string(),
"Log out"
}
}
}
Err(error) => {
rsx! {
div { format!{"Failed to load disconnection url: {:?}", error} }
}
}
},
None => {
rsx! { div { "Loading... Please wait" } }
}
},
None => {
let logout_url_task = move || {
cx.spawn({
let log_out_url_state = log_out_url_state.to_owned();
async move {
let logout_url = log_out_url(id_token).await;
let logout_url_option = Some(logout_url);
log_out_url_state.set(Some(logout_url_option));
}
})
};
logout_url_task();
rsx! { div{"Loading log out url... Please wait"}}
}
},
None => {
rsx! {{}}
}
},
None => {
rsx! {{}}
}
})
}
#[component]
pub fn RefreshToken(cx: Scope<ClientProps>) -> Element {
let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
let fermi_auth_token_read = fermi_auth_token.read().clone();
cx.render(match fermi_auth_token_read {
Some(fermi_auth_client_read) => match fermi_auth_client_read.refresh_token {
Some(refresh_token) => {
let fermi_auth_token = fermi_auth_token.to_owned();
let fermi_auth_request = fermi_auth_request.to_owned();
let client = cx.props.client.clone();
let exchange_refresh_token_spawn = move || {
cx.spawn({
async move {
let exchange_refresh_token =
exchange_refresh_token(client, refresh_token).await;
match exchange_refresh_token {
Ok(response_token) => {
AuthTokenState::persistent_set(
&fermi_auth_token,
Some(AuthTokenState {
id_token: response_token.id_token().cloned(),
refresh_token: response_token.refresh_token().cloned(),
}),
);
}
Err(_error) => {
AuthTokenState::persistent_set(
&fermi_auth_token,
Some(AuthTokenState::default()),
);
AuthRequestState::persistent_set(
&fermi_auth_request,
Some(AuthRequestState::default()),
);
}
}
}
})
};
exchange_refresh_token_spawn();
rsx! { div { "Refreshing session, please wait" } }
}
None => {
rsx! { div { "Id token expired and no refresh token found" } }
}
},
None => {
rsx! {{}}
}
})
}
#[component]
pub fn LoadClient(cx: Scope) -> Element {
let init_client_future = use_future(cx, (), |_| async move { init_oidc_client().await });
let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(cx, &FERMI_CLIENT);
cx.render(match init_client_future.value() {
Some(client_props) => match client_props {
Ok((client_id, client)) => {
*fermi_client.write() = ClientState {
oidc_client: Some(ClientProps::new(client_id.clone(), client.clone())),
};
rsx! {
div { "Client successfully loaded" }
Outlet::<Route> {}
}
}
Err(error) => {
rsx! {
div { format!{"Failed to load client: {:?}", error} }
log::info!{"Failed to load client: {:?}", error},
Outlet::<Route> {}
}
}
},
None => {
rsx! {
div {
div { "Loading client, please wait" }
Outlet::<Route> {}
}
}
}
})
}
#[component]
pub fn AuthHeader(cx: Scope) -> Element {
let auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(cx, &FERMI_CLIENT);
let client = fermi_client.read().oidc_client.clone();
let auth_request_read = fermi_auth_request.read().clone();
let auth_token_read = auth_token.read().clone();
cx.render(match (client, auth_request_read, auth_token_read) {
// We have everything we need to attempt to authenticate the user
(Some(client_props), Some(auth_request), Some(auth_token)) => {
match auth_request.auth_request {
Some(auth_request) => {
match auth_token.id_token {
Some(id_token) => {
match email(
client_props.client.clone(),
id_token.clone(),
auth_request.nonce.clone(),
) {
Ok(email) => {
rsx! {
div {
div { email }
LogOut { client_id: client_props.client_id, client: client_props.client }
Outlet::<Route> {}
}
}
}
// Id token failed to be decoded
Err(error) => match error {
// Id token failed to be decoded because it expired, we refresh it
openidconnect::ClaimsVerificationError::Expired(_message) => {
log::info!("Token expired");
rsx! {
div {
RefreshToken {client_id: client_props.client_id, client: client_props.client}
Outlet::<Route> {}
}
}
}
// Other issue with token decoding
_ => {
log::info!("Other issue with token");
rsx! {
div {
div { error.to_string() }
Outlet::<Route> {}
}
}
}
},
}
}
// User is not logged in
None => {
rsx! {
div {
Link { to: auth_request.authorize_url.clone(), "Log in" }
Outlet::<Route> {}
}
}
}
}
}
None => {
let auth_request = authorize_url(client_props.client);
AuthRequestState::persistent_set(
fermi_auth_request,
Some(AuthRequestState {
auth_request: Some(auth_request),
}),
);
rsx! { div { "Loading nonce" } }
}
}
}
// Client is not initialized yet, we need it for everything
(None, _, _) => {
rsx! { LoadClient {} }
}
// We need everything loaded before doing anything
(_client, _auth_request, _auth_token) => {
rsx! {{}}
}
})
}