The Yew project is a framework for creating web application in Rust. Similar to what ReactJS is in the JavaScript world. Of course, some web application will require integration with a single sign-on technology like OAuth2 or OpenID connect (OIDC). And of course, no one is really fond to start a new project by implementing some OIDC token handshake first.
yew-oauth2
is a crate which does exactly that. Implement an OAuth2 or OpenID connect login flow using Yew components. Not making any assumption on the rest of the stack you’re going to use.
OAuth2 and OpenID connect
Both of those technologies have been around for a while now. There is a good number of implementations, both on the side of the server (the part managing the user identities), and libraries which help implement solutions on the client side.
I know the terminology in the land of OAuth2 is a bit different. Then again, while it may be more correct, it also can be quite confusing. Especially when you’re new to the matter. So for the rest of the post, I will try to keep it more simple, and might sprinkle in a few of those terms when it seems useful.
OpenID connect is a more specialized, or opinionated version of OAuth2. There are some additional expectations and APIs around the idea of how to use OAuth2. But in the core, it still is OAuth2. That’s why yew-oauth2
contains “oauth2” in its name, but also can handle OIDC.
The basics
For both OAuth2 and OIDC, there are different ways of performing authentication. There are two important things: first of all, this is about “authentication”. Ensuring to under “who” the user is, proving the user’s identity. But not “authorizing” a specific operation, the user would like to perform.
Second, we are only considering the “Authorization Code Flow”. This one is designed for single page applications, which we are talking about in the context of Yew.
So the mission is to detect if the user is authenticated. And if not, and it is required to send the user off to the login page of the SSO server. That server will perform the actual authentication, and then redirect the user back to us, including a “code,” which we can then trade for tokens. Those tokens will allow us to prove our identity to other backend services. And in the case of OIDC, we can also use such tokens to perform other operations, like refreshing our access token automatically.
Getting started
All that you need is to add the yew-oauth2
crate to your project, and then add the OAuth2
component near the root of your component tree:
#[function_component(MyApplication)]
fn my_app() -> Html {
let config = Config {
client_id: "my-client".into(),
auth_url: "https://my-sso/auth/realms/my-realm/protocol/openid-connect/auth".into(),
token_url: "https://my-sso/auth/realms/my-realm/protocol/openid-connect/token".into(),
};
html!(
<OAuth2 {config}>
<MyApplicationMain/>
</OAuth2>
)
}
This is an example using OAuth2, but it works quite similarly with OIDC. It defines a configuration of your OAuth2 setup, and then uses the component OAuth2
to inject a context (OAuth2Context
) into the tree which will give you access the state of the session. It does not yet trigger any login flow.
The context will not only allow you to get the current state of the session, but also allow you to initiate the login flow. With that, you can show or hide different components, based on the session state. And you can, either manually or automatically, start the login flow. For example:
let agent = use_auth_agent().expect("Requires OAuth2Context component in parent hierarchy");
let login = use_callback (agent.clone(), |agent| {
if let Err(err) = agent.start_login() {
log::warn!("Failed to start login: {err}");
}
});
Note, that calling start_login
will forward the user to the SSO server, effectively leaving your page.
More components and hooks
Of course, that is very basic so far. And while it provides the core operations for OAuth2 and OIDC, the crate also provides some more components, which give you some more out-of-the-box tools for common cases.
For example, when communicating with the backend, you most likely require just the “access token,” and don’t are much about the other session information. That’s what the use_latest_access_token()
hook is for.
Or, you want to show content only when the user is authenticated (without triggering the login flow), you can use the Authenticated
component like this:
html!(
<Authenticated>
<p> <button onclick={logout}>{ "Logout" }</button> </p>
</Authenticated>
<NotAuthenticated>
<p> { "You need to log in" } </p>
<p> <button onclick={login.clone()}>{ "Login" }</button> </p>
</NotAuthenticated>
)
More examples
As the matter can be quite complex, there are two example applications as part of the project. They are intended to launch a full project on your local machine, using Keycloak as the server. Of course, it will work with all kinds of other OAuth2 or OIDC compatible servers, but with Keycloak you can easily run that on your local machine, fully configured for the example applications.
The two examples differ slightly in the way they present themselves for the user. One hides everything unless you’re authenticated. While the other shows you some navigation structure, but then automatically redirects you when necessary.
The README.md
file has a more detailed description, as well as some instructions on how to get these examples started.
By default, both examples have the openid
feature flag enabled. And so they default to using OIDC. As Keycloak provides full OIDC support, that’s what makes sense for the default setup. But of course, using Rust’s feature system, you can also disable the feature and fall back to pure OAuth2 mode.
More information
Implementing OAuth2 or OIDC support with Yew is pretty simple now. If you need some more information, here are some links that might help:
- https://github.com/ctron/yew-oauth2 — GitHub repository
- https://yew.rs/ — Yew project homepage
- https://www.keycloak.org/ — Keycloak project homepage