Secure Leptos applications using Keycloak.
- OpenID Connect discovery
- Authorization code flow with PKCE, Nonce validation, and Logout validation
- ID token verification
- ID token introspection
- Automatic refresh token renewal
- Automatic access token usage and 401 response handling when using the provided
reqwest-basedAuthenticatedClient - Programmatic logout
- SSR support (auth flow still on client only)
This library has to create random numbers. It uses the rand crate for this. rand depends on getrandom.
For wasm32-unknown-unknown, the target of our hydrating client, a special getrandom wasm backend must be specified.
- With the current set of dependencies, two versions of
getrandomwill be in the dependency tree. We have to enablegetrandoms wasm related features on both to mitigate any compile errors. Add this to the Cargo.toml of your application.[target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3" getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] } getrandom_03 = { package = "getrandom", version = "0.3", features = ["wasm_js"] }
- Add
leptos-keycloak-authas a dependency and enable itsssrfeature when running on the server.[dependencies] leptos-keycloak-auth = "0.11" [features] hydrate = [ #... ] ssr = [ "leptos/ssr", "leptos-keycloak-auth/ssr", #... ]
Initialize auth at the root of your application with the <AuthProvider> component.
Note: expected_audiences defaults to ["<client>"] and expected_issuers defaults to
["<realm>"], so you don't need to specify them for standard setups. Redirect URLs automatically track
the current page, so wherever you display a login link the user will be redirected to automatically once the login was
completed on the Keycloak side.
This should happen between Router and Routes, so that initialization runs on all pages but has access ot the router
hook.
Use the MaybeAuthenticated, Authenticated and Unauthenticated component to conditionally render content based on
the current authentication status. These give you immediate access to the relevant state.
Use use_keycloak_auth or try_use_keycloak_auth to access leptos-keycloak-auth's main state. The try variant can
be used when you don't use the <AuthProvider> on all pages are unsure whether the library was initialized.
Use use_authenticated or try_use_authenticated to directly access the Authenticated state, providing information
about the user. The try variant can be used if you are unsure whether the user is currently logged in.
use leptos::prelude::*;
use leptos_router::{path, components::*};
use leptos_keycloak_auth::components::*;
use leptos_keycloak_auth::url::Url;
use leptos_keycloak_auth::hooks::use_keycloak_auth;
#[component]
pub fn App() -> impl IntoView {
view! {
<Router>
<AuthProvider
keycloak_server_url=Url::parse("http://localhost:8443").unwrap()
realm="my-realm"
client="my-client"
>
<Routes fallback=|| view! { "Page not found." }>
<Route path=path!("/") view=HomePage/>
<Route path=path!("/protected") view=ProtectedPage/>
</Routes>
</AuthProvider>
</Router>
}
}
#[component]
pub fn HomePage() -> impl IntoView {
view! {
<h1>"Welcome"</h1>
<MaybeAuthenticated
authenticated=|auth| view! {
<p>"Hello, " { auth.id_token_claims.read().name }</p>
}
unauthenticated=|| view! { <LoginButton/> }
/>
}
}
#[component]
pub fn ProtectedPage() -> impl IntoView {
view! {
<Authenticated fallback=|| view! { <LoginButton/> }>
{|auth| view! {
<p>"Welcome, " { auth.id_token_claims.read().name }</p>
}}
</Authenticated>
}
}
#[component]
pub fn LoginButton() -> impl IntoView {
let auth = use_keycloak_auth();
let login_url = move || auth.login_url.get().map(|u| u.to_string()).unwrap_or_default();
let login_url_unavailable = move || auth.login_url.get().is_none();
view! {
<a href=login_url aria-disabled=login_url_unavailable>"Log In"</a>
}
}Make sure that
- Podman is running
cargo-leptosis up to date
Start the tests (including the integration test) with
cd leptos-keycloak-auth
cargo test -- --nocaptureThis allows you to see the output from Keycloak as well as our test-frontend (build and running server)
when running the integration tests.
You can set DELAY_TEST_EXECUTION to true in integration_test.rs to play around with the test application.
You can than still run the UI test by entering y and pressing enter or canceling the test with n.
| Crate version | Compatible Leptos version |
|---|---|
| 0.1 | 0.6 |
| 0.2 | 0.6 |
| 0.3 - 0.6 | 0.7 |
| 0.7 - 0.11 | 0.8 |
- Starting from version
0.8.0, the minimum supported rust version is1.88.0 - Starting from version
0.6.0, the minimum supported rust version is1.85.0 - Starting from version
0.3.0, the minimum supported rust version is1.81.0
Q: My app does not compile due to getrandom not compiling for the wasm target.
A: Add this to your projects Cargo.toml
[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3"
getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] }
getrandom_03 = { package = "getrandom", version = "0.3", features = ["wasm_js"] }
Q: My app no longer compiles using an Apple Silicon chip (M1 or upwards) after including this crate.
A: This crate depends on jsonwebtoken which depends on ring which needs to compile C code in its build-script.
MacOS comes with its own (rather quirky) version of Clang, which often leads to weird issues. Make sure to use Clang
provided through the llvm installation. Follow these
instructions: briansmith/ring#1824 (comment)
brew install llvm
echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.zshrc
The crate was initially based on the fantastic work of leptos_oidc. Definitely check this out as well if you do not want a Keycloak specific dependency.