Internet Identity with Rust UI

Hello folks is it possible to implement Internet Identity on a UI that is Rust based (no JS, CSS, HTML or any JS Framework).

internet_identity.rs

use ic_cdk::api::call::call;
use ic_cdk::export::Principal;
use serde::{Deserialize, Serialize};
use candid::{CandidType, Decode, Encode};

#[derive(CandidType, Deserialize, Debug)]
pub struct AuthResponse {
    pub principal: Principal,
    pub success: bool,
    pub error: Option<String>,
}

pub async fn authenticate_user() -> Result<AuthResponse, String> {
    let canister_id = Principal::from_text("your_internet_identity_canister_id").unwrap();
    let result: Result<(AuthResponse,), (ic_cdk::api::call::RejectionCode, String)> = call(canister_id, "authenticate", ()).await;
    result.map(|(response,)| response).map_err(|e| e.1)
}

authentication_ui.rs

use crate::authentication::internet_identity::authenticate_user;
use crate::i3m::gui::{
    button::{ButtonBuilder, ButtonMessage},
    grid::{Column, GridBuilder, Row},
    message::{MessageDirection, UiMessage},
    text::{TextBuilder, TextMessage},
    widget::{WidgetBuilder},
    BuildContext, UiNode, UserInterface,
};
use std::sync::Mutex;
use lazy_static::lazy_static;
use ic_cdk::export::Principal;
use i3m_core::pool::Handle;

lazy_static! {
    static ref AUTHENTICATED_USER: Mutex<Option<Principal>> = Mutex::new(None);
}

pub struct AuthenticationUI {
    root: Handle<UiNode>,
    login_button: Handle<UiNode>,
    status_text: Handle<UiNode>,
}

impl AuthenticationUI {
    pub fn new(ctx: &mut BuildContext) -> Self {
        let status_text = TextBuilder::new(WidgetBuilder::new())
            .with_text("Please authenticate using Internet Identity...")
            .build(ctx);

        let login_button = ButtonBuilder::new(WidgetBuilder::new())
            .with_text("Login")
            .build(ctx);

        let root = GridBuilder::new(
            WidgetBuilder::new()
                .with_child(status_text)
                .with_child(login_button),
        )
        .add_row(Row::stretch())
        .add_row(Row::stretch())
        .add_column(Column::stretch())
        .build(ctx);

        Self {
            root,
            login_button,
            status_text,
        }
    }

    pub fn handle_message(&mut self, ui: &mut UserInterface, message: &UiMessage) {
        if let Some(ButtonMessage::Click) = message.data() {
            if message.destination() == self.login_button {
                self.authenticate(ui);
            }
        }
    }

    fn authenticate(&self, ui: &mut UserInterface) {
        let future = async {
            match authenticate_user().await {
                Ok(response) => {
                    if response.success {
                        let mut user = AUTHENTICATED_USER.lock().unwrap();
                        *user = Some(response.principal);
                        ui.send_message(TextMessage::text(
                            self.status_text,
                            MessageDirection::ToWidget,
                            format!("User authenticated: {:?}", response.principal),
                        ));
                        // start_editor().await;
                    } else {
                        ui.send_message(TextMessage::text(
                            self.status_text,
                            MessageDirection::ToWidget,
                            format!("Authentication failed: {:?}", response.error),
                        ));
                    }
                }
                Err(e) => {
                    ui.send_message(TextMessage::text(
                        self.status_text,
                        MessageDirection::ToWidget,
                        format!("Authentication error: {:?}", e),
                    ));
                }
            }
        };

        // Spawn the future to run asynchronously
        crate::i3m::core::futures::executor::block_on(future);
    }
}

1 Like

Internet Identity uses a browser popup that communicates with the dapp using PostMessage.

The flow is basically like this:

  1. Dapp creates local keypair
  2. Dapp opens popup window to II
  3. Send public key from dapp window to popup window
  4. Popup window sends delegation to dapp window
  5. Dapp makes IC calls by sending the payload signed with private key from step 1 + delegation from step 4, in agent-rs this is basically done with the DelegationIdentity

The delegation is basically the public key from step 1 signed with the private key from II. (In case of II, a BLS canister signature).

Opening a browser window (for II) and communicating back and forth with this window using PostMessage should be something you can find more documentation (and libs) about online.

As for the technical details, have a look at the source of the agent-js AuthClient agent-js/packages/auth-client/src/index.ts at main · dfinity/agent-js · GitHub

Also it seems someone already did all of the above and made a rust lib available crates.io: Rust Package Registry (edit: This rust lib is meant for browser environments, if your rust app is for example a desktop app, that doesn’t render in a webview, you’ll likely need to adapt it)

3 Likes

I’m basically building a gaming engine that is built majorly on Rust, what I’m trying to do is make the engine decentralized on ICP, so yes it’s a Desktop App

ic-auth-client crate is currently heavily dependent on Web API, so it should not work in environments without access to it.
However, I could help build it for Desktop Apps, and extend the crate or release it as a new crate.

edit:
It would be helpful to refer to structs contained in that crate for your purpose.

2 Likes

That would be great. It will save me the hustle

1 Like