{"id":11397,"date":"2023-05-10T10:36:42","date_gmt":"2023-05-10T10:36:42","guid":{"rendered":"https:\/\/codevoweb.com\/?p=11397"},"modified":"2024-02-06T11:10:19","modified_gmt":"2024-02-06T11:10:19","slug":"implement-google-and-github-oauth2-in-rust-frontend-app","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/implement-google-and-github-oauth2-in-rust-frontend-app\/","title":{"rendered":"Implement Google and GitHub OAuth2 in Rust Frontend App"},"content":{"rendered":"\n<p>In this article, you will learn how to implement OAuth for Google and GitHub in a Rust frontend application using the Yew.rs framework. Additionally, I will provide backend APIs built using Rust, Node.js, and Golang that you can use alongside the frontend application.<\/p>\n\n\n\n<p>While the Rust backend has support for both Google and GitHub OAuth, only Google OAuth is currently functioning correctly. Therefore, if you intend to use GitHub OAuth, I recommend running the frontend app with either the Node.js or Golang backend APIs.<\/p>\n\n\n\n<p>As this article may be lengthy, feel free to skip to the section that demonstrates <a href=\"\/implement-google-and-github-oauth2-in-rust-frontend-app\/#generate-the-github-and-google-oauth-consent-screen-urls\">how to implement Google and GitHub OAuth<\/a>. However, if you wish to add other credential options where the user can sign up and sign in with email and password, then you can read the remaining content, which shows how it can be done.<\/p>\n\n\n\n<p>To keep this article concise, I will focus on explaining only the most relevant concepts. However, if you need further explanation on any topic covered in this tutorial, please don&#8217;t hesitate to ask. Simply leave a comment below and I&#8217;ll do my best to provide you with the information you need. With that said, let&#8217;s dive right into the tutorial.<\/p>\n\n\n\n<p>More practice:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/rust-how-to-generate-and-verify-jwts-json-web-tokens\/\">Rust &#8211; How to Generate and Verify (JWTs) JSON Web Tokens<\/a><\/li>\n\n\n\n<li><a href=\"\/how-to-implement-github-oauth-in-reactjs\/\">How to Implement GitHub OAuth in React.js<\/a><\/li>\n\n\n\n<li><a href=\"\/how-to-implement-google-oauth2-in-reactjs\/\">How to Implement Google OAuth2 in React.js<\/a><\/li>\n\n\n\n<li><a href=\"\/how-to-implement-github-oauth-in-nodejs\/\">How to Implement GitHub OAuth in Node.js<\/a><\/li>\n\n\n\n<li><a href=\"\/how-to-implement-google-oauth2-in-nodejs\/\">How to Implement Google OAuth2 in Node.js<\/a><\/li>\n\n\n\n<li><a href=\"\/how-to-implement-google-oauth2-in-golang\/\">How to Implement Google OAuth2 in Golang<\/a><\/li>\n\n\n\n<li><a href=\"\/google-oauth-authentication-react-and-node\/\">Google OAuth Authentication React.js and Node.js(No Passport)<\/a><\/li>\n\n\n\n<li><a href=\"\/github-oauth-authentication-react-and-node\/\">GitHub OAuth Authentication React.js and Node.js(No Passport)<\/a><\/li>\n\n\n\n<li><a href=\"\/github-oauth-authentication-vue-mongodb-and-golang\">GitHub OAuth Authentication Vuejs, MongoDB and Golang<\/a><\/li>\n\n\n\n<li><a href=\"\/google-oauth-authentication-react-mongodb-and-golang\/\">Google OAuth Authentication React.js, MongoDB and Golang<\/a><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"850\" height=\"478\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Implement-Google-and-GitHub-OAuth2-in-Rust-Frontend-App.webp\" alt=\"Implement Google and GitHub OAuth2 in Rust Frontend App\" class=\"wp-image-11415\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Implement-Google-and-GitHub-OAuth2-in-Rust-Frontend-App.webp 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Implement-Google-and-GitHub-OAuth2-in-Rust-Frontend-App-300x169.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Implement-Google-and-GitHub-OAuth2-in-Rust-Frontend-App-768x432.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Implement-Google-and-GitHub-OAuth2-in-Rust-Frontend-App-100x56.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Implement-Google-and-GitHub-OAuth2-in-Rust-Frontend-App-700x394.webp 700w\" sizes=\"auto, (max-width: 850px) 100vw, 850px\" \/><\/figure>\n\n\n<style>.kb-table-of-content-nav.kb-table-of-content-id11397_a098f9-11 .kb-table-of-content-wrap{padding-top:var(--global-kb-spacing-sm, 1.5rem);padding-right:var(--global-kb-spacing-sm, 1.5rem);padding-bottom:var(--global-kb-spacing-sm, 1.5rem);padding-left:var(--global-kb-spacing-sm, 1.5rem);border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}.kb-table-of-content-nav.kb-table-of-content-id11397_a098f9-11 .kb-table-of-contents-title-wrap{padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px;}.kb-table-of-content-nav.kb-table-of-content-id11397_a098f9-11 .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id11397_a098f9-11 .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id11397_a098f9-11 .kb-table-of-content-wrap .kb-table-of-content-list{color:#ffffff;font-weight:regular;font-style:normal;margin-top:var(--global-kb-spacing-sm, 1.5rem);margin-right:0px;margin-bottom:0px;margin-left:0px;}@media all and (max-width: 1024px){.kb-table-of-content-nav.kb-table-of-content-id11397_a098f9-11 .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}@media all and (max-width: 767px){.kb-table-of-content-nav.kb-table-of-content-id11397_a098f9-11 .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}<\/style>\n\n\n<h2 class=\"wp-block-heading\">Run the Google and GitHub OAuth Frontend App<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Make sure that you have both <a href=\"https:\/\/trunkrs.dev\/\" target=\"_blank\" rel=\"noreferrer noopener\">Trunk<\/a> and the WebAssembly target installed. You can install them by following the instructions provided on the Yew.rs documentation&#8217;s &#8220;<a href=\"https:\/\/yew.rs\/docs\/getting-started\/introduction\" target=\"_blank\" rel=\"noreferrer noopener\">Getting Started<\/a>&#8221; page.<\/li>\n\n\n\n<li>Download or clone the Yew.rs project from <a href=\"https:\/\/github.com\/wpcodevo\/rust-yew-google-github-oauth2\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/wpcodevo\/rust-yew-google-github-oauth2<\/a> and open it in your preferred code editor.<\/li>\n\n\n\n<li>Duplicate the <code>example.env<\/code> file and rename the copied one to <code>.env<\/code>. Then, add your Google and GitHub OAuth client IDs to the <code>.env<\/code> file. If you don&#8217;t have these IDs yet or are unsure of how to obtain them, don&#8217;t worry! The last two sections of this article provide a step-by-step guide that will walk you through the process.<\/li>\n\n\n\n<li>Open your code editor&#8217;s integrated terminal and run the command <code>trunk build<\/code>. This command installs the necessary crates and creates asset-build pipelines for the assets specified in the <code>index.html<\/code> file.<\/li>\n\n\n\n<li>Once the installation and asset build process is complete, start the Yew web server by running <code>trunk serve --port 3000<\/code> in the terminal. This command creates a local web server on your machine, which you can access via your web browser at <code>localhost:3000<\/code>.<\/li>\n\n\n\n<li>To interact with the app&#8217;s features, such as signing in with Google and GitHub OAuth, registering and signing in with email and password, logging out from the app, and requesting your account details, you need to set up a backend server that the Yew app can make requests to. <br><br>Follow the steps outlined in the subsequent sections to set up backend servers with Rust, Node.js, and Golang.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Run a Rust Backend Server with the App<\/h2>\n\n\n\n<p>The Rust backend project has built-in support for both Google and GitHub OAuth, but at the moment, only Google OAuth is fully functional. There is an issue with the GitHub implementation that I am still trying to resolve. The problem appears to be in the <code>get_github_user<\/code> function located in the <code>\/src\/github_oauth.rs<\/code> file. Despite the code being correct, obtaining user information using the access token from GitHub returns a 403 error.<\/p>\n\n\n\n<p>If you want to test both Google and GitHub OAuth, I suggest setting up a <a href=\"https:\/\/github.com\/wpcodevo\/google-github-oauth2-nodejs\" target=\"_blank\" rel=\"noreferrer noopener\">Node.js<\/a> or <a href=\"https:\/\/github.com\/wpcodevo\/google-github-oauth2-golang\" target=\"_blank\" rel=\"noreferrer noopener\">Golang server<\/a> that supports both by following the instructions in the &#8220;<a href=\"\/implement-google-and-github-oauth2-in-rust-frontend-app\/#other-backend-apis-with-oauth-support\">Other Backend APIs with OAuth Support<\/a>&#8221; section below. Also, please leave a comment if you discover a solution to the GitHub OAuth issue in the Rust project, so I can update the project accordingly.<\/p>\n\n\n\n<p>In the meantime, if you&#8217;re looking for a comprehensive guide on implementing Google OAuth in Rust, I suggest checking out the post titled &#8220;<a href=\"\/how-to-implement-google-oauth2-in-rust\/\">How to Implement Google OAuth2 in Rust<\/a>&#8220;. Alternatively, you can quickly set up the API and interact with it using the Yew.rs application by following the instructions below, without having to write any Rust backend code.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Download or clone the Rust Google and GitHub OAuth2 project from <a href=\"https:\/\/github.com\/wpcodevo\/google-github-oauth2-rust\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/wpcodevo\/google-github-oauth2-rust<\/a> and open the source code in your preferred code editor.<\/li>\n\n\n\n<li>Edit the <code>.env<\/code> file to include your Google and GitHub OAuth client IDs and secrets. If you&#8217;re unsure how to obtain these credentials, you can follow the instructions in the last two sections of this article to get them.<\/li>\n\n\n\n<li>After adding the OAuth credentials to the <code>.env<\/code> file, install the packages and dependencies required by the project by running the command <code>cargo build<\/code>.<\/li>\n\n\n\n<li>Start the Actix-Web HTTP server by running the command <code>cargo run<\/code>.<\/li>\n\n\n\n<li>With the Rust backend server ready to accept requests, you can use the Yew.rs app to interact with it.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Other Backend APIs with OAuth Support<\/h2>\n\n\n\n<p>Although I originally intended to include instructions for running the Node.js or Golang server in this article, I ultimately decided against it due to length concerns. However, I have written separate articles that cover these topics, which you can find through the links below.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>For Node.js: <a href=\"\/how-to-implement-google-oauth2-in-nodejs\/\">How to Implement Google OAuth2 in Node.js<\/a><\/li>\n\n\n\n<li>For Golang: <a href=\"\/how-to-implement-google-oauth2-in-golang\">How to Implement Google OAuth2 in Golang<\/a><\/li>\n<\/ul>\n\n\n\n<p>It&#8217;s not necessary to read the entire article, only follow the section that provides instructions on running the project without writing a single line of code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How the GitHub and Google OAuth Works<\/h2>\n\n\n\n<p>Before we dive into the implementation of Google and GitHub OAuth in the Rust frontend project, let me give you an overview of how the OAuth flow works.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Sign In using Google OAuth<\/h3>\n\n\n\n<p>Assuming you struggle with remembering passwords, you can take advantage of the Google OAuth option to sign in to the application without using a password. To use Google OAuth, simply click on the &#8220;<strong>CONTINUE WITH GOOGLE<\/strong>&#8221; button.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"948\" height=\"1024\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/The-Login-Page-of-the-Yew.rs-Frontend-App-948x1024.webp\" alt=\"The Login Page of the Yew.rs Frontend App\" class=\"wp-image-11404\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/The-Login-Page-of-the-Yew.rs-Frontend-App-948x1024.webp 948w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/The-Login-Page-of-the-Yew.rs-Frontend-App-278x300.webp 278w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/The-Login-Page-of-the-Yew.rs-Frontend-App-768x829.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/The-Login-Page-of-the-Yew.rs-Frontend-App-93x100.webp 93w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/The-Login-Page-of-the-Yew.rs-Frontend-App-417x450.webp 417w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/The-Login-Page-of-the-Yew.rs-Frontend-App.webp 977w\" sizes=\"auto, (max-width: 948px) 100vw, 948px\" \/><\/figure>\n\n\n\n<p>After that, the Yew.rs app will generate a Google OAuth consent screen URL and redirect you to it. On the consent screen, you will be prompted to select an account, which will grant the Yew application access to your public Google information such as your email, name, profile picture, and more.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"856\" height=\"1024\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/vuejs-google-oauth-authentication-consent-screen-856x1024.png\" alt=\"vuejs google oauth authentication consent screen\" class=\"wp-image-2546\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/vuejs-google-oauth-authentication-consent-screen-856x1024.png 856w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/vuejs-google-oauth-authentication-consent-screen-251x300.png 251w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/vuejs-google-oauth-authentication-consent-screen-768x919.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/vuejs-google-oauth-authentication-consent-screen-84x100.png 84w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/vuejs-google-oauth-authentication-consent-screen-376x450.png 376w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/06\/vuejs-google-oauth-authentication-consent-screen.png 903w\" sizes=\"auto, (max-width: 856px) 100vw, 856px\" \/><\/figure>\n\n\n\n<p>The Google authorization API will then authenticate the request and redirect it to the callback URL you provided during the application registration process. The callback URL is an endpoint on the backend server that makes additional requests to the Google OAuth endpoints and returns the user&#8217;s profile information to the frontend app.<\/p>\n\n\n\n<p>Once the frontend app receives the information, it will redirect you to the profile page where you can view your profile details.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Sign In using GitHub OAuth<\/h3>\n\n\n\n<p>In a similar manner, if you prefer to sign into the application using the GitHub OAuth option, you can simply click on the &#8220;<strong>CONTINUE WITH GITHUB<\/strong>&#8221; button. The Yew.rs application will then generate a GitHub OAuth consent screen URL and redirect you to it.<\/p>\n\n\n\n<p>On the consent screen, you will be prompted to authorize access to the Yew application. Once you grant access, GitHub will redirect the request to the redirect URL you provided when you registered the application on GitHub. From there, the backend server will take over, making additional requests to GitHub and returning the GitHub user&#8217;s information to the frontend app.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"959\" height=\"937\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/GitHub-OAuth-Authorization-Screen-for-the-NextAuth-Project.webp\" alt=\"GitHub OAuth Authorization Screen for the NextAuth Project\" class=\"wp-image-11173\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/GitHub-OAuth-Authorization-Screen-for-the-NextAuth-Project.webp 959w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/GitHub-OAuth-Authorization-Screen-for-the-NextAuth-Project-300x293.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/GitHub-OAuth-Authorization-Screen-for-the-NextAuth-Project-768x750.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/GitHub-OAuth-Authorization-Screen-for-the-NextAuth-Project-100x98.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/GitHub-OAuth-Authorization-Screen-for-the-NextAuth-Project-461x450.webp 461w\" sizes=\"auto, (max-width: 959px) 100vw, 959px\" \/><\/figure>\n\n\n\n<p>After the Yew app receives the user&#8217;s credentials, it will redirect you to the profile page where your credentials will be displayed.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"817\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Access-the-Account-Page-After-the-OAuth-Authentication-is-Successfull-1024x817.webp\" alt=\"Access the Account Page After the OAuth Authentication is Successfull\" class=\"wp-image-11406\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Access-the-Account-Page-After-the-OAuth-Authentication-is-Successfull-1024x817.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Access-the-Account-Page-After-the-OAuth-Authentication-is-Successfull-300x239.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Access-the-Account-Page-After-the-OAuth-Authentication-is-Successfull-768x613.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Access-the-Account-Page-After-the-OAuth-Authentication-is-Successfull-100x80.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Access-the-Account-Page-After-the-OAuth-Authentication-is-Successfull-564x450.webp 564w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/Access-the-Account-Page-After-the-OAuth-Authentication-is-Successfull.webp 1038w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Setup the Rust Frontend Project<\/h2>\n\n\n\n<p>Once you finish this project, your file and folder organization should resemble the example shown in the screenshot below. This can serve as a helpful reference as you follow along with the instructions outlined in this article.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"829\" height=\"944\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/File-and-Folder-Structure-of-the-Rust-Frontend-Google-and-GitHub-OAuth-Project.webp\" alt=\"File and Folder Structure of the Rust Frontend Google and GitHub OAuth Project\" class=\"wp-image-11407\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/File-and-Folder-Structure-of-the-Rust-Frontend-Google-and-GitHub-OAuth-Project.webp 829w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/File-and-Folder-Structure-of-the-Rust-Frontend-Google-and-GitHub-OAuth-Project-263x300.webp 263w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/File-and-Folder-Structure-of-the-Rust-Frontend-Google-and-GitHub-OAuth-Project-768x875.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/File-and-Folder-Structure-of-the-Rust-Frontend-Google-and-GitHub-OAuth-Project-88x100.webp 88w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/File-and-Folder-Structure-of-the-Rust-Frontend-Google-and-GitHub-OAuth-Project-395x450.webp 395w\" sizes=\"auto, (max-width: 829px) 100vw, 829px\" \/><\/figure>\n\n\n\n<p>To get started, navigate to the directory where you&#8217;d like to create your Rust project and open a new terminal in that location. Then, execute the following commands:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\">\n<code>\nmkdir rust-yew-google-github-oauth2\ncd rust-yew-google-github-oauth2\ncargo init\n<\/code>\n<\/pre>\n\n\n\n<p>This will create a new folder named <code>rust-yew-google-github-oauth2<\/code>, which will serve as your project&#8217;s root directory. The <code>cargo init<\/code> command will then initialize the folder as a Rust project, creating a new <strong>Cargo.toml<\/strong> file and a &#8216;<strong>src<\/strong>&#8216; directory. Finally, you can install the required crates for this project by running the following commands:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\">\n<code>\ncargo add yew --features csr\ncargo add serde_json\ncargo add serde --features derive\ncargo add chrono --features serde\ncargo add reqwasm\ncargo add yew-router\ncargo add gloo\ncargo add validator --features derive\ncargo add yewdux\ncargo add wasm-bindgen\ncargo add web-sys --features \"HtmlInputElement Window\"\ncargo add wasm-bindgen-futures\ncargo add url\ncargo add load-dotenv\n<\/code>\n<\/pre>\n\n\n\n<p>If the latest version of any of the crates causes issues with your application, you can revert to the version specified in the <code>Cargo.toml<\/code> file below.<\/p>\n\n\n\n<p><strong>Cargo.toml<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\">\n<code>\n[package]\nname = \"rust-yew-google-github-oauth2\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https:\/\/doc.rust-lang.org\/cargo\/reference\/manifest.html\n\n[dependencies]\nchrono = { version = \"0.4.24\", features = [\"serde\"] }\ngloo = \"0.8.0\"\nload-dotenv = \"0.1.2\"\nreqwasm = \"0.5.0\"\nserde = { version = \"1.0.160\", features = [\"derive\"] }\nserde_json = \"1.0.96\"\nurl = \"2.3.1\"\nvalidator = { version = \"0.16.0\", features = [\"derive\"] }\nwasm-bindgen = \"0.2.84\"\nwasm-bindgen-futures = \"0.4.34\"\nweb-sys = { version = \"0.3.61\", features = [\"HtmlInputElement\", \"Window\"] }\nyew = { version = \"0.20.0\", features = [\"csr\"] }\nyew-router = \"0.17.0\"\nyewdux = \"0.9.2\"\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Generate the GitHub and Google OAuth Consent Screen URLs<\/h2>\n\n\n\n<p>To generate the Google and GitHub OAuth consent screen URLs, we&#8217;ll start by creating utility functions. Before we can do that, we need to add the OAuth client IDs to the <code>.env<\/code> file.<\/p>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\">\n<code>\nSERVER_ENDPOINT=http:\/\/localhost:8000\n\nGOOGLE_OAUTH_CLIENT_ID=\nGOOGLE_OAUTH_REDIRECT_URL=http:\/\/localhost:8000\/api\/sessions\/oauth\/google\n\nGITHUB_OAUTH_CLIENT_ID=\nGITHUB_OAUTH_REDIRECT_URL=http:\/\/localhost:8000\/api\/sessions\/oauth\/github\n<\/code>\n<\/pre>\n\n\n\n<p>To use the OAuth client IDs in our utility functions, we need to load them from the environment variables. You might think we could use the <code><a href=\"https:\/\/crates.io\/crates\/dotenv\" target=\"_blank\" rel=\"noreferrer noopener\">dotenv<\/a><\/code> crate, which is a popular choice for loading environment variables. However, this won&#8217;t work for our project since the frontend application will be running in the browser, and <code>dotenv<\/code> depends on runtime evaluation.<\/p>\n\n\n\n<p>Instead, we&#8217;ll use another crate called <code><a href=\"https:\/\/crates.io\/crates\/load-dotenv\" target=\"_blank\" rel=\"noreferrer noopener\">load-dotenv<\/a><\/code> that loads environment variables at compile time, and enables us to read them using the <code>std::env!()<\/code> macro.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Function to Generate the Google OAuth Consent Screen<\/h3>\n\n\n\n<p>Let&#8217;s create a function that generates the URL for the Google OAuth consent screen. This function will be named <code>get_google_url<\/code> and will accept a path as an argument, which is the URL where the user should be redirected after authentication is complete.<\/p>\n\n\n\n<p>Inside the function, we&#8217;ll use a <code>HashMap<\/code> to store the necessary parameters for the Google OAuth consent screen. Then, we&#8217;ll construct the URL and return it from the function.<\/p>\n\n\n\n<p>To create this function, navigate to the &#8216;src&#8217; directory and create a new folder called &#8216;utils&#8217;. Inside the &#8216;utils&#8217; folder, create a file named <code>get_google_url.rs<\/code> and add the code provided below.<\/p>\n\n\n\n<p><strong>src\/utils\/get_google_url.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\">\n<code>\nuse std::collections::HashMap;\nuse url::Url;\n\nuse load_dotenv::load_dotenv;\n\nload_dotenv!();\n\npub fn get_google_url(from: Option&lt;&amp;str&gt;) -&gt; String {\n    let client_id = std::env!(&quot;GOOGLE_OAUTH_CLIENT_ID&quot;);\n    let redirect_uri = std::env!(&quot;GOOGLE_OAUTH_REDIRECT_URL&quot;);\n\n    let root_url = &quot;https:\/\/accounts.google.com\/o\/oauth2\/v2\/auth&quot;;\n    let mut options = HashMap::new();\n    options.insert(&quot;redirect_uri&quot;, redirect_uri);\n    options.insert(&quot;client_id&quot;, client_id);\n    options.insert(&quot;access_type&quot;, &quot;offline&quot;);\n    options.insert(&quot;response_type&quot;, &quot;code&quot;);\n    options.insert(&quot;prompt&quot;, &quot;consent&quot;);\n    options.insert(\n        &quot;scope&quot;,\n        &quot;https:\/\/www.googleapis.com\/auth\/userinfo.profile https:\/\/www.googleapis.com\/auth\/userinfo.email&quot;,\n    );\n    options.insert(&quot;state&quot;, from.unwrap_or_default());\n\n    let url = Url::parse_with_params(root_url, &amp;options).unwrap();\n    let qs = url.query().unwrap();\n\n    format!(&quot;{}?{}&quot;, root_url, qs)\n}\n<\/code>\n<\/pre>\n\n\n\n<p>With the <code>get_google_url<\/code> function defined, you can now use it in the <code>href={}<\/code> attribute of the Google OAuth button to generate the appropriate URL. When the user clicks on the button, they will be redirected to the Google OAuth consent screen, where they will be asked to grant permission for the application to access their Google account.<\/p>\n\n\n\n<p>Here&#8217;s an example code snippet that shows how to use the <code>get_google_url<\/code> function to create a Google OAuth button with the appropriate link:<\/p>\n\n\n\n<pre class=\"line-numbers language-html\">\n<code>\n&lt;a\n  class=&quot;&quot;\n  style={ &quot;background-color: #3b5998&quot; }\n  href={get_google_url(Some(&quot;\/profile&quot;))}\n  role=&quot;button&quot;\n&gt;\n  &lt;img\n    class=&quot;pr-2&quot;\n    src=&quot;assets\/google.svg&quot;\n    alt=&quot;&quot;\n      style={&quot;height: 2.2rem&quot;}\n  \/&gt;\n  {&quot; Continue with Google&quot;}\n&lt;\/a&gt;\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Function to Generate the GitHub OAuth Consent Screen<\/h3>\n\n\n\n<p>Next, we&#8217;ll create a utility function <code>get_github_url<\/code> that generates the URL for the GitHub OAuth consent screen. This function takes an optional path argument that specifies where to redirect the user after authentication.<\/p>\n\n\n\n<p>To create the function, we&#8217;ll follow a similar logic as <code>get_google_url<\/code>. First, we&#8217;ll load the client ID and redirect URL from environment variables using <code>std::env!()<\/code>. Then, we&#8217;ll create a <code>HashMap<\/code> to hold the parameters required by the GitHub OAuth consent screen. Finally, we&#8217;ll construct the URL using the <code>Url<\/code> crate and return it from the function.<\/p>\n\n\n\n<p>To implement <code>get_github_url<\/code>, create a new file <code>get_github_url.rs<\/code> in the <code>src\/utils<\/code> directory, and add the following code:<\/p>\n\n\n\n<p><strong>src\/utils\/get_github_url.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\">\n<code>\nuse std::collections::HashMap;\nuse url::Url;\n\nuse load_dotenv::load_dotenv;\n\nload_dotenv!();\n\npub fn get_github_url(from: Option&lt;&amp;str&gt;) -&gt; String {\n    let client_id = std::env!(&quot;GITHUB_OAUTH_CLIENT_ID&quot;);\n    let redirect_uri = std::env!(&quot;GITHUB_OAUTH_REDIRECT_URL&quot;);\n\n    let root_url = &quot;https:\/\/github.com\/login\/oauth\/authorize&quot;;\n    let mut options = HashMap::new();\n    options.insert(&quot;redirect_uri&quot;, redirect_uri);\n    options.insert(&quot;client_id&quot;, client_id);\n    options.insert(&quot;scope&quot;, &quot;user:email&quot;);\n    options.insert(&quot;state&quot;, from.unwrap_or_default());\n\n    let url = Url::parse_with_params(root_url, &amp;options).unwrap();\n    let qs = url.query().unwrap();\n\n    format!(&quot;{}?{}&quot;, root_url, qs)\n}\n<\/code>\n<\/pre>\n\n\n\n<p>Having defined the <code>get_github_url<\/code> function, you can now use it to generate the appropriate URL for the GitHub OAuth consent screen in the <code>href={}<\/code> attribute of your GitHub OAuth button. When the user clicks on the button, they will be redirected to the GitHub OAuth consent screen, where they will be asked to grant permission for the application to access their GitHub account.<\/p>\n\n\n\n<p>Here&#8217;s an example code snippet that shows how to use the <code>get_github_url<\/code> function to create a GitHub OAuth button with the appropriate link:<\/p>\n\n\n\n<pre class=\"line-numbers language-html\">\n<code>\n&lt;a\n  class=&quot;&quot;\n  style={&quot;background-color:#55acee&quot;}\n  href={get_github_url(Some(&quot;\/profile&quot;))}\n  role=&quot;button&quot;\n&gt;\n  &lt;img\n    class=&quot;pr-2&quot;\n    src=&quot;assets\/github.svg&quot;\n    alt=&quot;&quot;\n    style={&quot;height: 2.2rem&quot;}\n  \/&gt;\n  {&quot;Continue with GitHub&quot;}\n&lt;\/a&gt;\n<\/code>\n<\/pre>\n\n\n\n<p>To allow the utility functions to be used throughout your code, you&#8217;ll need to create a <code>mod.rs<\/code> file in the <code>src\/utils<\/code> directory and export the utility function files as modules. Here&#8217;s an example code snippet that shows how to do this:<\/p>\n\n\n\n<p><strong>src\/utils\/mod.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\">\n<code>\npub mod get_github_url;\npub mod get_google_url;\n<\/code>\n<\/pre>\n\n\n\n<p>That&#8217;s it! You now have a good understanding of how to implement the frontend logic for Google and GitHub OAuth. But, if you&#8217;re interested in taking things further and building pages where users can sign up and sign in using their email and password, then keep reading the article to learn more.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Set up Tailwind CSS for Styling<\/h2>\n\n\n\n<p>To make our website look pretty, we&#8217;re going to use Tailwind CSS. It&#8217;s a popular tool for styling web pages, and even though it&#8217;s mostly used with JavaScript, we can still use it in our Rust project. To get started, just run this command:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\r\nnpx tailwindcss init -p\r\n<\/code>\r\n<\/pre>\n\n\n\n<p>By running the above command, two new files <code>tailwind.config.js<\/code> and &#8216;<strong>postcss.config.js&#8217;<\/strong> will be generated in the project&#8217;s root directory. We need to update the <code>tailwind.config.js<\/code> file with specific configurations to help Tailwind CSS recognize the utility classes in our Rust templates. Simply replace the existing configurations in the file with the new ones provided below.<\/p>\n\n\n\n<p><strong>tailwind.config.js<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\n\/** @type {import('tailwindcss').Config} *\/\nmodule.exports = {\n  content: [\n    \".\/index.html\",\n    \".\/src\/**\/*.{rs,html}\"\n  ],\n  theme: {\n    extend: {\n      colors: {\n        'ct-dark-600': '#222',\n        'ct-dark-200': '#e5e7eb',\n        'ct-dark-100': '#f5f6f7',\n        'ct-blue-600': '#2363eb',\n        'ct-yellow-600': '#f9d13e',\n      },\n      fontFamily: {\n        Poppins: ['Poppins, sans-serif'],\n      },\n      container: {\n        center: true,\n        padding: '1rem',\n        screens: {\n          lg: '1125px',\n          xl: '1125px',\n          '2xl': '1125px',\n        },\n      },\n    },\n  },\n  plugins: [],\n};\n<\/code>\n<\/pre>\n\n\n\n<p>To generate the utility classes used in our Rust templates, we need to add Tailwind CSS directives to a CSS file. To achieve this, create a &#8216;<strong>styles<\/strong>&#8216; folder in the root directory and within this folder, create a <code>tailwind.css<\/code> file. Then, copy and paste the following code into the newly created file.<\/p>\n\n\n\n<p><strong>styles\/tailwind.css<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-css\"><code>\n@import url('https:\/\/fonts.googleapis.com\/css2?family=Poppins:wght@300;400;500;600;700&display=swap');\n\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml {\n    font-family: 'Poppins', sans-serif;\n}\n<\/code>\n<\/pre>\n\n\n\n<p>Now, we need to set up a way to automatically generate the utility classes when any changes are made to the Rust templates. Luckily, we can achieve this using Trunk&#8217;s post-build hook feature. To get started, create a <code>Trunk.toml<\/code> file in the project&#8217;s root directory, and add the following configurations to it:<\/p>\n\n\n\n<p><strong>Trunk.toml<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\n[[hooks]]\nstage = \"post_build\"\ncommand = \"sh\"\ncommand_arguments = [\"-c\", \"npx tailwindcss -i .\/styles\/tailwind.css -o .\/dist\/.stage\/index.css\"]\n<\/code>\n<\/pre>\n\n\n\n<p>Finally, to complete the setup, we need to create an <code>index.html<\/code> file in the project&#8217;s root directory that Trunk will use to drive all asset building and bundling. We&#8217;ll also include the generated CSS file in the <code>index.html<\/code> so that our Tailwind CSS can be applied to the Rust templates. To do this, create an <code>index.html<\/code> file and add the following code to it:<\/p>\n\n\n\n<p><strong>index.html<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-html\"><code>\n&lt;!DOCTYPE html&gt;\n&lt;html lang=&quot;en&quot;&gt;\n  &lt;head&gt;\n    &lt;meta charset=&quot;UTF-8&quot; \/&gt;\n    &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot; \/&gt;\n    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; \/&gt;\n    &lt;link data-trunk rel=&quot;icon&quot; href=&quot;logo.svg&quot; type=&quot;image\/svg&quot; \/&gt;\n    &lt;link rel=&quot;stylesheet&quot; href=&quot;.\/index.css&quot; \/&gt;\n    &lt;link data-trunk rel=&quot;copy-dir&quot; href=&quot;.\/assets&quot; \/&gt;\n    &lt;title&gt;My First Yew App&lt;\/title&gt;\n  &lt;\/head&gt;\n  &lt;body&gt;&lt;\/body&gt;\n&lt;\/html&gt;\n<\/code>\n<\/pre>\n\n\n\n<p>The HTML code above includes a reference to an assets directory where we&#8217;ll store the images used in the project. To download this directory, <a href=\"https:\/\/minhaskamal.github.io\/DownGit\/#\/home?url=https:\/\/github.com\/wpcodevo\/rust-yew-google-github-oauth2\/tree\/master\/assets\" target=\"_blank\" rel=\"noreferrer noopener\">click on this link<\/a> and unzip the downloaded file. After that, move the assets folder into the project&#8217;s root directory.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create the API Request Functions<\/h2>\n\n\n\n<p>To enable communication with the backend server, we need to create API request functions. While these requests can be included in their respective components, it&#8217;s better to keep them in a separate module to allow for reuse in multiple components. <\/p>\n\n\n\n<p>We&#8217;ll start by defining some structs that represent the structure of the response from the backend server. To do this, navigate to the &#8216;<strong>src<\/strong>&#8216; directory, create a new folder named <code>api<\/code>, and add a file named <code>types.rs<\/code>. The structures provided below should be included in this file.<\/p>\n\n\n\n<p><strong>src\/api\/types.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse chrono::prelude::*;\nuse serde::{Deserialize, Serialize};\n\n#[allow(non_snake_case)]\n#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone)]\npub struct User {\n    pub id: String,\n    pub name: String,\n    pub email: String,\n    pub role: String,\n    pub photo: String,\n    pub provider: String,\n    pub verified: bool,\n    pub createdAt: DateTime&lt;Utc&gt;,\n    pub updatedAt: DateTime&lt;Utc&gt;,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct UserData {\n    pub user: User,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct UserResponse {\n    pub status: String,\n    pub data: UserData,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct UserLoginResponse {\n    pub status: String,\n    pub token: String,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct ErrorResponse {\n    pub status: String,\n    pub message: String,\n}\n<\/code>\n<\/pre>\n\n\n\n<p>After defining the necessary response structures in the <code>types.rs<\/code> file, we can create API request functions that allow us to register, log in, log out, and retrieve user profile information from the backend server. To keep these functions organized and reusable, we&#8217;ll create a new module named <code>user_api.rs<\/code> in the <code>src\/api<\/code> directory.<\/p>\n\n\n\n<p>Inside the <code>user_api.rs<\/code> file, we&#8217;ll define the four API request functions mentioned above. The register and login functions take user credentials as input and return user information upon successful authentication. The logout function does not take any input and simply logs the user out of the current session. Finally, the <code>api_user_info<\/code> function retrieves information about the currently authenticated user.<\/p>\n\n\n\n<p><strong>src\/api\/user_api.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse super::types::{ErrorResponse, User, UserLoginResponse, UserResponse};\nuse reqwasm::http;\n\npub async fn api_register_user(user_data: &amp;str) -&gt; Result&lt;User, String&gt; {\n    let response = match http::Request::post(&quot;http:\/\/localhost:8000\/api\/auth\/register&quot;)\n        .header(&quot;Content-Type&quot;, &quot;application\/json&quot;)\n        .body(user_data)\n        .send()\n        .await\n    {\n        Ok(res) =&gt; res,\n        Err(_) =&gt; return Err(&quot;Failed to make request&quot;.to_string()),\n    };\n\n    if !response.ok() {\n        let error_response = response.json::&lt;ErrorResponse&gt;().await;\n        if let Ok(error_response) = error_response {\n            return Err(error_response.message);\n        } else {\n            return Err(format!(&quot;API error: {}&quot;, response.status()));\n        }\n    }\n\n    let res_json = response.json::&lt;UserResponse&gt;().await;\n    match res_json {\n        Ok(data) =&gt; Ok(data.data.user),\n        Err(_) =&gt; Err(&quot;Failed to parse response&quot;.to_string()),\n    }\n}\n\npub async fn api_login_user(credentials: &amp;str) -&gt; Result&lt;UserLoginResponse, String&gt; {\n    let response = match http::Request::post(&quot;http:\/\/localhost:8000\/api\/auth\/login&quot;)\n        .header(&quot;Content-Type&quot;, &quot;application\/json&quot;)\n        .credentials(http::RequestCredentials::Include)\n        .body(credentials)\n        .send()\n        .await\n    {\n        Ok(res) =&gt; res,\n        Err(_) =&gt; return Err(&quot;Failed to make request&quot;.to_string()),\n    };\n\n    if response.status() != 200 {\n        let error_response = response.json::&lt;ErrorResponse&gt;().await;\n        if let Ok(error_response) = error_response {\n            return Err(error_response.message);\n        } else {\n            return Err(format!(&quot;API error: {}&quot;, response.status()));\n        }\n    }\n\n    let res_json = response.json::&lt;UserLoginResponse&gt;().await;\n    match res_json {\n        Ok(data) =&gt; Ok(data),\n        Err(_) =&gt; Err(&quot;Failed to parse response&quot;.to_string()),\n    }\n}\n\npub async fn api_user_info() -&gt; Result&lt;User, String&gt; {\n    let response = match http::Request::get(&quot;http:\/\/localhost:8000\/api\/users\/me&quot;)\n        .credentials(http::RequestCredentials::Include)\n        .send()\n        .await\n    {\n        Ok(res) =&gt; res,\n        Err(_) =&gt; return Err(&quot;Failed to make request&quot;.to_string()),\n    };\n\n    if response.status() != 200 {\n        let error_response = response.json::&lt;ErrorResponse&gt;().await;\n        if let Ok(error_response) = error_response {\n            return Err(error_response.message);\n        } else {\n            return Err(format!(&quot;API error: {}&quot;, response.status()));\n        }\n    }\n\n    let res_json = response.json::&lt;UserResponse&gt;().await;\n    match res_json {\n        Ok(data) =&gt; Ok(data.data.user),\n        Err(_) =&gt; Err(&quot;Failed to parse response&quot;.to_string()),\n    }\n}\n\npub async fn api_logout_user() -&gt; Result&lt;(), String&gt; {\n    let response = match http::Request::get(&quot;http:\/\/localhost:8000\/api\/auth\/logout&quot;)\n        .credentials(http::RequestCredentials::Include)\n        .send()\n        .await\n    {\n        Ok(res) =&gt; res,\n        Err(_) =&gt; return Err(&quot;Failed to make request&quot;.to_string()),\n    };\n\n    if response.status() != 200 {\n        let error_response = response.json::&lt;ErrorResponse&gt;().await;\n        if let Ok(error_response) = error_response {\n            return Err(error_response.message);\n        } else {\n            return Err(format!(&quot;API error: {}&quot;, response.status()));\n        }\n    }\n\n    Ok(())\n}\n<\/code>\n<\/pre>\n\n\n\n<p>With the API request functions and structs defined, the next step is to make them accessible to other parts of the application. To do this, create a new file called <code>mod.rs<\/code> in the <code>src\/api<\/code> folder and use it to export the <code>types<\/code> and <code>user_api<\/code> modules. This will allow other components in the application to use these modules and their associated functions.<\/p>\n\n\n\n<p><strong>src\/api\/mod.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\npub mod types;\npub mod user_api;\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Manage the Application State Globally<\/h2>\n\n\n\n<p>Now, let&#8217;s move on to managing the application state. There are various ways to accomplish this task, such as using the Context API provided by Yew. However, for this project, we will use the Yewdux library as it comes with many useful features. We will define the functions that modify the states along with the states themselves in a single file for better organization. So, to get started, create a <code>store.rs<\/code> file in the <code>src<\/code> directory and add the code below to it.<\/p>\n\n\n\n<p><strong>src\/store.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse serde::{Deserialize, Serialize};\nuse yewdux::prelude::*;\n\nuse crate::api::types::User;\n\n#[derive(Debug, PartialEq, Serialize, Deserialize, Default, Clone)]\npub struct AlertInput {\n    pub show_alert: bool,\n    pub alert_message: String,\n}\n\n#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Store)]\npub struct Store {\n    pub auth_user: Option&lt;User&gt;,\n    pub page_loading: bool,\n    pub alert_input: AlertInput,\n}\n\npub fn set_page_loading(loading: bool, dispatch: Dispatch&lt;Store&gt;) {\n    dispatch.reduce_mut(move |store| {\n        store.page_loading = loading;\n    })\n}\n\npub fn set_auth_user(user: Option&lt;User&gt;, dispatch: Dispatch&lt;Store&gt;) {\n    dispatch.reduce_mut(move |store| {\n        store.auth_user = user;\n    })\n}\n\npub fn set_show_alert(message: String, dispatch: Dispatch&lt;Store&gt;) {\n    dispatch.reduce_mut(move |store| {\n        store.alert_input = AlertInput {\n            alert_message: message,\n            show_alert: true,\n        };\n    })\n}\n\npub fn set_hide_alert(dispatch: Dispatch&lt;Store&gt;) {\n    dispatch.reduce_mut(move |store| {\n        store.alert_input.show_alert = false;\n    })\n}\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create Some Reusable Components<\/h2>\n\n\n\n<p>As we continue building out our web app, it&#8217;s important to keep in mind the principles of DRY (Don&#8217;t Repeat Yourself) and modularity. One way to accomplish this is by creating reusable components that can be used in multiple parts of our application. <\/p>\n\n\n\n<p>In this case, we&#8217;ll be creating several key components, including a Spinner, Form input, Alert notification, and Header component. By doing so, we can save time and effort by avoiding repetitive code, and maintain a more organized and scalable codebase.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Create a Spinner Component<\/h3>\n\n\n\n<p>To implement the Spinner component, we&#8217;ll begin by creating a new directory called &#8216;<strong>components<\/strong>&#8216; inside the &#8216;<strong>src<\/strong>&#8216; folder. Within the &#8216;<strong>components<\/strong>&#8216; folder, we&#8217;ll then create a <code>spinner.rs<\/code> file and add the code below. This component will be responsible for displaying a spinner whenever a request is being processed by the backend server.<\/p>\n\n\n\n<p><strong>src\/components\/spinner.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse yew::prelude::*;\n\n#[derive(Debug, Properties, PartialEq)]\npub struct Props {\n    #[prop_or(&quot;1.25rem&quot;.to_string())]\n    pub width: String,\n    #[prop_or(&quot;1.25rem&quot;.to_string())]\n    pub height: String,\n    #[prop_or(&quot;text-gray-200&quot;.to_string())]\n    pub color: String,\n    #[prop_or(&quot;fill-blue-600&quot;.to_string())]\n    pub bg_color: String,\n}\n\n#[function_component(Spinner)]\npub fn spinner_component(props: &amp;Props) -&gt; Html {\n    html! {\n    &lt;svg\n      role=&quot;status&quot;\n      class={format!(\n        &quot;mr-2 {} animate-spin dark:text-gray-600 {} h-5&quot;,\n        props.color.clone(), props.bg_color.clone()\n      )}\n      style={format!(&quot;height:{};width:{}&quot;, props.width.clone(), props.height.clone())}\n      viewBox=&quot;0 0 100 101&quot;\n      fill=&quot;none&quot;\n      xmlns=&quot;http:\/\/www.w3.org\/2000\/svg&quot;\n    &gt;\n      &lt;path\n        d=&quot;M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z&quot;\n        fill=&quot;currentColor&quot;\n      \/&gt;\n      &lt;path\n        d=&quot;M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z&quot;\n        fill=&quot;currentFill&quot;\n      \/&gt;\n    &lt;\/svg&gt;\n    }\n}\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Create a Form Input Component<\/h3>\n\n\n\n<p>Let&#8217;s move on to creating the Form Input component, which we can reuse in any form that needs an input field. To define the component, create a new file named <code>form_input.rs<\/code> in the <code>src\/components<\/code> directory and copy the code provided below.<\/p>\n\n\n\n<p><strong>src\/components\/form_input.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse std::{cell::RefCell, rc::Rc};\n\nuse validator::ValidationErrors;\nuse wasm_bindgen::JsCast;\nuse web_sys::HtmlInputElement;\nuse yew::prelude::*;\n\n#[derive(Properties, PartialEq)]\npub struct Props {\n    #[prop_or(&quot;text&quot;.to_string())]\n    pub input_type: String,\n    pub label: String,\n    pub name: String,\n    pub input_ref: NodeRef,\n    pub handle_onchange: Callback&lt;String&gt;,\n    pub handle_on_input_blur: Callback&lt;(String, String)&gt;,\n    pub errors: Rc&lt;RefCell&lt;ValidationErrors&gt;&gt;,\n}\n\n#[function_component(FormInput)]\npub fn form_input_component(props: &amp;Props) -&gt; Html {\n    let val_errors = props.errors.borrow();\n    let errors = val_errors.field_errors().clone();\n    let empty_errors = vec![];\n    let error = match errors.get(&amp;props.name.as_str()) {\n        Some(error) =&gt; error,\n        None =&gt; &amp;empty_errors,\n    };\n    let error_message = match error.get(0) {\n        Some(message) =&gt; message.to_string(),\n        None =&gt; &quot;&quot;.to_string(),\n    };\n\n    let handle_onchange = props.handle_onchange.clone();\n    let onchange = Callback::from(move |event: Event| {\n        let target = event.target().unwrap();\n        let value = target.unchecked_into::&lt;HtmlInputElement&gt;().value();\n        handle_onchange.emit(value);\n    });\n\n    let handle_on_input_blur = props.handle_on_input_blur.clone();\n    let on_blur = {\n        let cloned_input_name = props.name.clone();\n        Callback::from(move |event: FocusEvent| {\n            let input_name = cloned_input_name.clone();\n            let target = event.target().unwrap();\n            let value = target.unchecked_into::&lt;HtmlInputElement&gt;().value();\n            handle_on_input_blur.emit((input_name, value));\n        })\n    };\n\n    html! {\n    &lt;div class=&quot;mb-6&quot;&gt;\n      &lt;input\n        type={props.input_type.clone()}\n        placeholder={props.label.clone()}\n        class=&quot;form-control block w-full px-4 py-5 text-sm font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none&quot;\n        ref={props.input_ref.clone()}\n        onchange={onchange}\n        onblur={on_blur}\n      \/&gt;\n    &lt;span class=&quot;text-red-700 text-sm mt-1&quot;&gt;\n        {error_message}\n    &lt;\/span&gt;\n    &lt;\/div&gt;\n    }\n}\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Create an Alert Notification Component<\/h3>\n\n\n\n<p>Let&#8217;s now implement an alert notification component that can display success or error messages returned by the backend server. To get started, create a new file named <code>alert.rs<\/code> within the <code>src\/components<\/code> directory and paste the following code:<\/p>\n\n\n\n<p><strong>src\/components\/alert.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse gloo::timers::callback::Timeout;\nuse yew::prelude::*;\nuse yewdux::prelude::use_store;\n\nuse crate::store::{set_hide_alert, Store};\n\n#[derive(Debug, PartialEq, Properties)]\npub struct Props {\n    pub message: String,\n    pub delay_ms: u32,\n}\n\n#[function_component(AlertComponent)]\npub fn alert_component(props: &amp;Props) -&gt; Html {\n    let (store, dispatch) = use_store::&lt;Store&gt;();\n    let show_alert = store.alert_input.show_alert;\n\n    use_effect_with_deps(\n        move |(show_alert, dispatch, delay_ms)| {\n            let cloned_dispatch = dispatch.clone();\n            if *show_alert {\n                let handle =\n                    Timeout::new(*delay_ms, move || set_hide_alert(cloned_dispatch)).forget();\n                let clear_handle = move || {\n                    web_sys::Window::clear_timeout_with_handle(\n                        &amp;web_sys::window().unwrap(),\n                        handle.as_f64().unwrap() as i32,\n                    );\n                };\n\n                Box::new(clear_handle) as Box&lt;dyn FnOnce()&gt;\n            } else {\n                Box::new(|| {}) as Box&lt;dyn FnOnce()&gt;\n            }\n        },\n        (show_alert, dispatch.clone(), props.delay_ms),\n    );\n\n    html! {\n    &lt;div id=&quot;myToast&quot; class={format!(&quot;fixed top-14 right-10 px-5 py-4 border-r-8 border-orange-500 bg-white drop-shadow-lg {}&quot;, if show_alert { &quot;&quot; } else { &quot;hidden&quot; })}&gt;\n        &lt;p class=&quot;text-sm&quot;&gt;\n            &lt;span class=&quot;mr-2 inline-block px-3 py-1 rounded-full bg-blue-500 text-white font-extrabold&quot;&gt;{&quot;i&quot;}&lt;\/span&gt;\n            {props.message.clone()}\n        &lt;\/p&gt;\n    &lt;\/div&gt;\n    }\n}\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Create a Header Component<\/h3>\n\n\n\n<p>To enable us to reuse the header across different page components and provide the functionality to sign out the currently logged-in user, we&#8217;ll create a <code>Header<\/code> component. This component will contain a navigation menu with links to different pages, depending on whether the user is logged in or not. To create this component, we&#8217;ll add the following code to a new <code>header.rs<\/code> file located in the <code>src\/components<\/code> directory.<\/p>\n\n\n\n<p><strong>src\/components\/header.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse crate::{\n    api::user_api::api_logout_user,\n    router::{self, Route},\n    store::{set_auth_user, set_page_loading, set_show_alert, Store},\n};\nuse wasm_bindgen_futures::spawn_local;\nuse yew::prelude::*;\nuse yew_router::prelude::*;\nuse yewdux::prelude::*;\n\n#[function_component(Header)]\npub fn header_component() -&gt; Html {\n    let (store, dispatch) = use_store::&lt;Store&gt;();\n    let user = store.auth_user.clone();\n    let navigator = use_navigator().unwrap();\n\n    let handle_logout = {\n        let store_dispatch = dispatch.clone();\n        let cloned_navigator = navigator.clone();\n\n        Callback::from(move |_: MouseEvent| {\n            let dispatch = store_dispatch.clone();\n            let navigator = cloned_navigator.clone();\n            spawn_local(async move {\n                set_page_loading(true, dispatch.clone());\n                let res = api_logout_user().await;\n                match res {\n                    Ok(_) =&gt; {\n                        set_page_loading(false, dispatch.clone());\n                        set_auth_user(None, dispatch.clone());\n                        set_show_alert(&quot;Logged out successfully&quot;.to_string(), dispatch);\n                        navigator.push(&amp;router::Route::LoginPage);\n                    }\n                    Err(e) =&gt; {\n                        set_show_alert(e.to_string(), dispatch.clone());\n                        set_page_loading(false, dispatch);\n                    }\n                };\n            });\n        })\n    };\n\n    html! {\n        &lt;header class=&quot;bg-white h-20&quot;&gt;\n        &lt;nav class=&quot;h-full flex justify-between container items-center&quot;&gt;\n          &lt;div&gt;\n            &lt;Link&lt;Route&gt; to={Route::HomePage} classes=&quot;text-ct-dark-600&quot;&gt;{&quot;CodevoWeb&quot;}&lt;\/Link&lt;Route&gt;&gt;\n          &lt;\/div&gt;\n          &lt;ul class=&quot;flex items-center gap-4&quot;&gt;\n            &lt;li&gt;\n              &lt;Link&lt;Route&gt; to={Route::HomePage} classes=&quot;text-ct-dark-600&quot;&gt;{&quot;Home&quot;}&lt;\/Link&lt;Route&gt;&gt;\n            &lt;\/li&gt;\n            if user.is_some() {\n               &lt;&gt;\n                &lt;li&gt;\n                  &lt;Link&lt;Route&gt; to={Route::ProfilePage} classes=&quot;text-ct-dark-600&quot;&gt;{&quot;Profile&quot;}&lt;\/Link&lt;Route&gt;&gt;\n                &lt;\/li&gt;\n                &lt;li\n                  class=&quot;cursor-pointer&quot;\n                &gt;\n                  {&quot;Create Post&quot;}\n                &lt;\/li&gt;\n                &lt;li class=&quot;cursor-pointer&quot; onclick={handle_logout}&gt;\n                  {&quot;Logout&quot;}\n                &lt;\/li&gt;\n              &lt;\/&gt;\n\n            } else {\n              &lt;&gt;\n                &lt;li&gt;\n                  &lt;Link&lt;Route&gt; to={Route::RegisterPage} classes=&quot;text-ct-dark-600&quot;&gt;{&quot;SignUp&quot;}&lt;\/Link&lt;Route&gt;&gt;\n                &lt;\/li&gt;\n                &lt;li&gt;\n                  &lt;Link&lt;Route&gt; to={Route::LoginPage} classes=&quot;text-ct-dark-600&quot;&gt;{&quot;Login&quot;}&lt;\/Link&lt;Route&gt;&gt;\n                &lt;\/li&gt;\n              &lt;\/&gt;\n            }\n          &lt;\/ul&gt;\n        &lt;\/nav&gt;\n      &lt;\/header&gt;\n    }\n}\n<\/code>\n<\/pre>\n\n\n\n<p>To make these reusable components accessible to other parts of the application, create a <code>mod.rs<\/code> file in the <code>src\/components<\/code> directory and add the code below to export them.<\/p>\n\n\n\n<p><strong>src\/components\/mod.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\npub mod alert;\npub mod form_input;\npub mod header;\npub mod spinner;\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create the Frontend Pages for the Authentication<\/h2>\n\n\n\n<p>We&#8217;ve completed most of the groundwork for our application by building the API request functions, designing reusable Yew components, and defining the application states using Yewdux. Now, we can integrate these parts by creating page components that leverage them. The pages we&#8217;ll create include the login page, account registration page, home page, and profile page.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Account Registration Page<\/h3>\n\n\n\n<p>To begin, let&#8217;s focus on creating the signup page component. This component will enable users to input their details and create a new account within our application. To get started, you&#8217;ll need to create a &#8216;<strong>pages<\/strong>&#8216; directory in the &#8216;<strong>src<\/strong>&#8216; folder. Within this &#8216;<strong>pages<\/strong>&#8216; directory, you can create a <code>register_page.rs<\/code> file and add the necessary code to create the component.<\/p>\n\n\n\n<p><strong>src\/pages\/register_page.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse std::cell::RefCell;\nuse std::ops::Deref;\nuse std::rc::Rc;\n\nuse crate::api::user_api::api_register_user;\nuse crate::components::form_input::FormInput;\nuse crate::components::header::Header;\nuse crate::router;\nuse crate::store::{set_page_loading, set_show_alert, Store};\n\nuse serde::{Deserialize, Serialize};\nuse validator::{Validate, ValidationErrors};\nuse wasm_bindgen_futures::spawn_local;\nuse web_sys::HtmlInputElement;\nuse yew::prelude::*;\nuse yew_router::prelude::*;\nuse yewdux::prelude::*;\n\n#[derive(Validate, Debug, Default, Clone, Serialize, Deserialize)]\n\nstruct RegisterUserSchema {\n    #[validate(length(min = 1, message = &quot;Name is required&quot;))]\n    name: String,\n    #[validate(\n        length(min = 1, message = &quot;Email is required&quot;),\n        email(message = &quot;Email is invalid&quot;)\n    )]\n    email: String,\n    #[validate(\n        length(min = 1, message = &quot;Password is required&quot;),\n        length(min = 6, message = &quot;Password must be at least 6 characters&quot;)\n    )]\n    password: String,\n    #[validate(\n        length(min = 1, message = &quot;Please confirm your password&quot;),\n        must_match(other = &quot;password&quot;, message = &quot;Passwords do not match&quot;)\n    )]\n    password_confirm: String,\n}\n\nfn get_input_callback(\n    name: &amp;&#039;static str,\n    cloned_form: UseStateHandle&lt;RegisterUserSchema&gt;,\n) -&gt; Callback&lt;String&gt; {\n    Callback::from(move |value| {\n        let mut data = cloned_form.deref().clone();\n        match name {\n            &quot;name&quot; =&gt; data.name = value,\n            &quot;email&quot; =&gt; data.email = value,\n            &quot;password&quot; =&gt; data.password = value,\n            &quot;password_confirm&quot; =&gt; data.password_confirm = value,\n            _ =&gt; (),\n        }\n        cloned_form.set(data);\n    })\n}\n\n#[function_component(RegisterPage)]\npub fn register_page() -&gt; Html {\n    let (_, dispatch) = use_store::&lt;Store&gt;();\n    let form = use_state(|| RegisterUserSchema::default());\n    let validation_errors = use_state(|| Rc::new(RefCell::new(ValidationErrors::new())));\n    let navigator = use_navigator().unwrap();\n\n    let name_input_ref = NodeRef::default();\n    let email_input_ref = NodeRef::default();\n    let password_input_ref = NodeRef::default();\n    let password_confirm_input_ref = NodeRef::default();\n\n    let validate_input_on_blur = {\n        let cloned_form = form.clone();\n        let cloned_validation_errors = validation_errors.clone();\n        Callback::from(move |(name, value): (String, String)| {\n            let mut data = cloned_form.deref().clone();\n            match name.as_str() {\n                &quot;email&quot; =&gt; data.email = value,\n                &quot;password&quot; =&gt; data.password = value,\n                _ =&gt; (),\n            }\n            cloned_form.set(data);\n\n            match cloned_form.validate() {\n                Ok(_) =&gt; {\n                    cloned_validation_errors\n                        .borrow_mut()\n                        .errors_mut()\n                        .remove(name.as_str());\n                }\n                Err(errors) =&gt; {\n                    cloned_validation_errors\n                        .borrow_mut()\n                        .errors_mut()\n                        .retain(|key, _| key != &amp;name);\n                    for (field_name, error) in errors.errors() {\n                        if field_name == &amp;name {\n                            cloned_validation_errors\n                                .borrow_mut()\n                                .errors_mut()\n                                .insert(field_name.clone(), error.clone());\n                        }\n                    }\n                }\n            }\n        })\n    };\n\n    let handle_name_input = get_input_callback(&quot;name&quot;, form.clone());\n    let handle_email_input = get_input_callback(&quot;email&quot;, form.clone());\n    let handle_password_input = get_input_callback(&quot;password&quot;, form.clone());\n    let handle_password_confirm_input = get_input_callback(&quot;password_confirm&quot;, form.clone());\n\n    let on_submit = {\n        let cloned_form = form.clone();\n        let cloned_validation_errors = validation_errors.clone();\n        let cloned_navigator = navigator.clone();\n        let cloned_dispatch = dispatch.clone();\n\n        let cloned_name_input_ref = name_input_ref.clone();\n        let cloned_email_input_ref = email_input_ref.clone();\n        let cloned_password_input_ref = password_input_ref.clone();\n        let cloned_password_confirm_input_ref = password_confirm_input_ref.clone();\n\n        Callback::from(move |event: SubmitEvent| {\n            let form = cloned_form.clone();\n            let validation_errors = cloned_validation_errors.clone();\n            let navigator = cloned_navigator.clone();\n            let dispatch = cloned_dispatch.clone();\n\n            let name_input_ref = cloned_name_input_ref.clone();\n            let email_input_ref = cloned_email_input_ref.clone();\n            let password_input_ref = cloned_password_input_ref.clone();\n            let password_confirm_input_ref = cloned_password_confirm_input_ref.clone();\n\n            event.prevent_default();\n            spawn_local(async move {\n                match form.validate() {\n                    Ok(_) =&gt; {\n                        let form_data = form.deref().clone();\n                        let form_json = serde_json::to_string(&amp;form_data).unwrap();\n                        set_page_loading(true, dispatch.clone());\n\n                        let name_input = name_input_ref.cast::&lt;HtmlInputElement&gt;().unwrap();\n                        let email_input = email_input_ref.cast::&lt;HtmlInputElement&gt;().unwrap();\n                        let password_input = password_input_ref.cast::&lt;HtmlInputElement&gt;().unwrap();\n                        let password_confirm_input = password_confirm_input_ref\n                            .cast::&lt;HtmlInputElement&gt;()\n                            .unwrap();\n\n                        name_input.set_value(&quot;&quot;);\n                        email_input.set_value(&quot;&quot;);\n                        password_input.set_value(&quot;&quot;);\n                        password_confirm_input.set_value(&quot;&quot;);\n\n                        let res = api_register_user(&amp;form_json).await;\n                        match res {\n                            Ok(_) =&gt; {\n                                set_page_loading(false, dispatch.clone());\n                                set_show_alert(\n                                    &quot;Account registered successfully&quot;.to_string(),\n                                    dispatch,\n                                );\n                                navigator.push(&amp;router::Route::LoginPage);\n                            }\n                            Err(e) =&gt; {\n                                set_page_loading(false, dispatch.clone());\n                                set_show_alert(e.to_string(), dispatch);\n                            }\n                        };\n                    }\n                    Err(e) =&gt; {\n                        validation_errors.set(Rc::new(RefCell::new(e)));\n                    }\n                }\n            });\n        })\n    };\n\n    html! {\n    &lt;&gt;\n    &lt;Header \/&gt;\n    &lt;section class=&quot;bg-ct-blue-600 min-h-screen pt-20&quot;&gt;\n      &lt;div class=&quot;container mx-auto px-6 py-12 h-full flex justify-center items-center&quot;&gt;\n        &lt;div class=&quot;md:w-8\/12 lg:w-5\/12 bg-white px-8 py-10&quot;&gt;\n          &lt;form onsubmit={on_submit}&gt;\n            &lt;FormInput label=&quot;Full Name&quot; name=&quot;name&quot; input_ref={name_input_ref} handle_onchange={handle_name_input}  errors={&amp;*validation_errors} handle_on_input_blur={validate_input_on_blur.clone()} \/&gt;\n            &lt;FormInput label=&quot;Email&quot; name=&quot;email&quot; input_type=&quot;email&quot; input_ref={email_input_ref} handle_onchange={handle_email_input}  errors={&amp;*validation_errors} handle_on_input_blur={validate_input_on_blur.clone()} \/&gt;\n            &lt;FormInput label=&quot;Password&quot; name=&quot;password&quot; input_type=&quot;password&quot; input_ref={password_input_ref} handle_onchange={handle_password_input}  errors={&amp;*validation_errors} handle_on_input_blur={validate_input_on_blur.clone()} \/&gt;\n            &lt;FormInput\n              label=&quot;Confirm Password&quot;\n              name=&quot;password_confirm&quot;\n              input_type=&quot;password&quot;\n              input_ref={password_confirm_input_ref}\n              handle_onchange={handle_password_confirm_input}\n              errors={&amp;*validation_errors}\n              handle_on_input_blur={validate_input_on_blur.clone()}\n            \/&gt;\n            &lt;button\n              type=&quot;submit&quot;\n              class=&quot;inline-block px-7 py-4 bg-blue-600 text-white font-medium text-sm leading-snug uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out w-full&quot;\n            &gt;\n              {&quot;Sign up&quot;}\n            &lt;\/button&gt;\n          &lt;\/form&gt;\n          &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/section&gt;\n    &lt;\/&gt;\n    }\n}\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Account Login Page<\/h3>\n\n\n\n<p>Let&#8217;s move on to creating the login page component, which serves a unique purpose in our application. This component features a form where registered users can enter their email and password to log in. In addition, it also provides the option for users to sign in through their Google or GitHub accounts using OAuth. <\/p>\n\n\n\n<p>To create the login page component, navigate to the <code>src\/pages<\/code> directory and create a new file called <code>login_page.rs<\/code>. Then, copy and paste the code below into the file.<\/p>\n\n\n\n<p><strong>src\/pages\/login_page.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse std::cell::RefCell;\nuse std::ops::Deref;\nuse std::rc::Rc;\n\nuse crate::api::user_api::api_login_user;\nuse crate::components::form_input::FormInput;\nuse crate::components::header::Header;\nuse crate::router;\nuse crate::store::{set_page_loading, set_show_alert, Store};\nuse crate::utils::get_github_url::get_github_url;\nuse crate::utils::get_google_url::get_google_url;\n\nuse serde::{Deserialize, Serialize};\nuse validator::{Validate, ValidationErrors};\nuse wasm_bindgen_futures::spawn_local;\nuse web_sys::HtmlInputElement;\nuse yew::prelude::*;\nuse yew_router::prelude::*;\nuse yewdux::prelude::*;\n\n#[derive(Validate, Debug, Default, Clone, Serialize, Deserialize)]\n\nstruct LoginUserSchema {\n    #[validate(\n        length(min = 1, message = &quot;Email is required&quot;),\n        email(message = &quot;Email is invalid&quot;)\n    )]\n    email: String,\n    #[validate(\n        length(min = 1, message = &quot;Password is required&quot;),\n        length(min = 6, message = &quot;Password must be at least 6 characters&quot;)\n    )]\n    password: String,\n}\n\nfn get_input_callback(\n    name: &amp;&#039;static str,\n    cloned_form: UseStateHandle&lt;LoginUserSchema&gt;,\n) -&gt; Callback&lt;String&gt; {\n    Callback::from(move |value| {\n        let mut data = cloned_form.deref().clone();\n        match name {\n            &quot;email&quot; =&gt; data.email = value,\n            &quot;password&quot; =&gt; data.password = value,\n            _ =&gt; (),\n        }\n        cloned_form.set(data);\n    })\n}\n\n#[function_component(LoginPage)]\npub fn login_page() -&gt; Html {\n    let (_, dispatch) = use_store::&lt;Store&gt;();\n    let form = use_state(|| LoginUserSchema::default());\n    let validation_errors = use_state(|| Rc::new(RefCell::new(ValidationErrors::new())));\n    let navigator = use_navigator().unwrap();\n\n    let email_input_ref = NodeRef::default();\n    let password_input_ref = NodeRef::default();\n\n    let validate_input_on_blur = {\n        let cloned_form = form.clone();\n        let cloned_validation_errors = validation_errors.clone();\n        Callback::from(move |(name, value): (String, String)| {\n            let mut data = cloned_form.deref().clone();\n            match name.as_str() {\n                &quot;email&quot; =&gt; data.email = value,\n                &quot;password&quot; =&gt; data.password = value,\n                _ =&gt; (),\n            }\n            cloned_form.set(data);\n\n            match cloned_form.validate() {\n                Ok(_) =&gt; {\n                    cloned_validation_errors\n                        .borrow_mut()\n                        .errors_mut()\n                        .remove(name.as_str());\n                }\n                Err(errors) =&gt; {\n                    cloned_validation_errors\n                        .borrow_mut()\n                        .errors_mut()\n                        .retain(|key, _| key != &amp;name);\n                    for (field_name, error) in errors.errors() {\n                        if field_name == &amp;name {\n                            cloned_validation_errors\n                                .borrow_mut()\n                                .errors_mut()\n                                .insert(field_name.clone(), error.clone());\n                        }\n                    }\n                }\n            }\n        })\n    };\n\n    let handle_email_input = get_input_callback(&quot;email&quot;, form.clone());\n    let handle_password_input = get_input_callback(&quot;password&quot;, form.clone());\n\n    let on_submit = {\n        let cloned_form = form.clone();\n        let cloned_validation_errors = validation_errors.clone();\n        let store_dispatch = dispatch.clone();\n        let cloned_navigator = navigator.clone();\n\n        let cloned_email_input_ref = email_input_ref.clone();\n        let cloned_password_input_ref = password_input_ref.clone();\n\n        Callback::from(move |event: SubmitEvent| {\n            event.prevent_default();\n\n            let dispatch = store_dispatch.clone();\n            let form = cloned_form.clone();\n            let validation_errors = cloned_validation_errors.clone();\n            let navigator = cloned_navigator.clone();\n\n            let email_input_ref = cloned_email_input_ref.clone();\n            let password_input_ref = cloned_password_input_ref.clone();\n\n            spawn_local(async move {\n                match form.validate() {\n                    Ok(_) =&gt; {\n                        let form_data = form.deref().clone();\n                        set_page_loading(true, dispatch.clone());\n\n                        let email_input = email_input_ref.cast::&lt;HtmlInputElement&gt;().unwrap();\n                        let password_input = password_input_ref.cast::&lt;HtmlInputElement&gt;().unwrap();\n\n                        email_input.set_value(&quot;&quot;);\n                        password_input.set_value(&quot;&quot;);\n\n                        let form_json = serde_json::to_string(&amp;form_data).unwrap();\n                        let res = api_login_user(&amp;form_json).await;\n                        match res {\n                            Ok(_) =&gt; {\n                                set_page_loading(false, dispatch);\n                                navigator.push(&amp;router::Route::ProfilePage);\n                            }\n                            Err(e) =&gt; {\n                                set_page_loading(false, dispatch.clone());\n                                set_show_alert(e.to_string(), dispatch);\n                            }\n                        };\n                    }\n                    Err(e) =&gt; {\n                        validation_errors.set(Rc::new(RefCell::new(e)));\n                    }\n                }\n            });\n        })\n    };\n\n    html! {\n    &lt;&gt;\n      &lt;Header \/&gt;\n      &lt;section class=&quot;bg-ct-blue-600 min-h-screen pt-20&quot;&gt;\n      &lt;div class=&quot;container mx-auto px-6 py-12 h-full flex justify-center items-center&quot;&gt;\n        &lt;div class=&quot;md:w-8\/12 lg:w-5\/12 bg-white px-8 py-10&quot;&gt;\n          &lt;form\n            onsubmit={on_submit}\n          &gt;\n            &lt;FormInput label=&quot;Email&quot; name=&quot;email&quot; input_type=&quot;email&quot; input_ref={email_input_ref} handle_onchange={handle_email_input} errors={&amp;*validation_errors} handle_on_input_blur={validate_input_on_blur.clone()} \/&gt;\n            &lt;FormInput label=&quot;Password&quot; name=&quot;password&quot; input_type=&quot;password&quot; input_ref={password_input_ref} handle_onchange={handle_password_input} errors={&amp;*validation_errors} handle_on_input_blur={validate_input_on_blur.clone()}\/&gt;\n\n            &lt;div class=&quot;flex justify-between items-center mb-6&quot;&gt;\n              &lt;div class=&quot;form-group form-check&quot;&gt;\n                &lt;input\n                  type=&quot;checkbox&quot;\n                  class=&quot;form-check-input appearance-none h-4 w-4 border border-gray-300 rounded-sm bg-white checked:bg-blue-600 checked:border-blue-600 focus:outline-none transition duration-200 mt-1 align-top bg-no-repeat bg-center bg-contain float-left mr-2 cursor-pointer&quot;\n                  id=&quot;exampleCheck3&quot;\n                \/&gt;\n                &lt;label\n                  class=&quot;form-check-label inline-block text-gray-800&quot;\n                  html=&quot;exampleCheck2&quot;\n                &gt;\n                  {&quot;Remember me&quot;}\n                &lt;\/label&gt;\n              &lt;\/div&gt;\n              &lt;a\n                href=&quot;#!&quot;\n                class=&quot;text-blue-600 hover:text-blue-700 focus:text-blue-700 active:text-blue-800 duration-200 transition ease-in-out&quot;\n              &gt;\n                {&quot;Forgot password?&quot;}\n              &lt;\/a&gt;\n            &lt;\/div&gt;\n\n            &lt;button\n              type=&quot;submit&quot;\n              class=&quot;inline-block px-7 py-4 bg-blue-600 text-white font-medium text-sm leading-snug uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out w-full&quot;\n            &gt;\n              {&quot;Sign in&quot;}\n            &lt;\/button&gt;\n\n            &lt;div class=&quot;flex items-center my-4 before:flex-1 before:border-t before:border-gray-300 before:mt-0.5 after:flex-1 after:border-t after:border-gray-300 after:mt-0.5&quot;&gt;\n              &lt;p class=&quot;text-center font-semibold mx-4 mb-0&quot;&gt;{&quot;OR&quot;}&lt;\/p&gt;\n            &lt;\/div&gt;\n\n            &lt;a\n              class=&quot;px-7 py-2 text-white font-medium text-sm leading-snug uppercase rounded shadow-md hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out w-full flex justify-center items-center mb-3&quot;\n              style={ &quot;background-color: #3b5998&quot; }\n              href={get_google_url(Some(&quot;\/profile&quot;))}\n              role=&quot;button&quot;\n            &gt;\n              &lt;img\n                class=&quot;pr-2&quot;\n                src=&quot;assets\/google.svg&quot;\n                alt=&quot;&quot;\n                 style={&quot;height: 2.2rem&quot;}\n              \/&gt;\n             {&quot; Continue with Google&quot;}\n            &lt;\/a&gt;\n            &lt;a\n              class=&quot;px-7 py-2 text-white font-medium text-sm leading-snug uppercase rounded shadow-md hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out w-full flex justify-center items-center&quot;\n              style={&quot;background-color:#55acee&quot;}\n              href={get_github_url(Some(&quot;\/profile&quot;))}\n              role=&quot;button&quot;\n            &gt;\n              &lt;img\n                class=&quot;pr-2&quot;\n                src=&quot;assets\/github.svg&quot;\n                alt=&quot;&quot;\n                style={&quot;height: 2.2rem&quot;}\n              \/&gt;\n              {&quot;Continue with GitHub&quot;}\n            &lt;\/a&gt;\n          &lt;\/form&gt;\n          &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/section&gt;\n    &lt;\/&gt;\n    }\n}\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Profile Page<\/h3>\n\n\n\n<p>Next, we&#8217;ll create the profile page component, which can only be accessed by logged-in users with valid cookies from the backend server. This page will display the account information of the currently logged-in user. To create this component, simply create a new file called <code>profile_page.rs<\/code> in the <code>src\/pages<\/code> directory, and add the provided code.<\/p>\n\n\n\n<p><strong>src\/pages\/profile_page.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse crate::{\n    api::user_api::api_user_info,\n    components::header::Header,\n    router,\n    store::{set_auth_user, set_page_loading, set_show_alert, Store},\n};\nuse yew::prelude::*;\nuse yew_router::prelude::use_navigator;\nuse yewdux::prelude::*;\n\n#[function_component(ProfilePage)]\npub fn profile_page() -&gt; Html {\n    let (store, dispatch) = use_store::&lt;Store&gt;();\n    let user = store.auth_user.clone();\n    let navigator = use_navigator().unwrap();\n\n    use_effect_with_deps(\n        move |_| {\n            let dispatch = dispatch.clone();\n            wasm_bindgen_futures::spawn_local(async move {\n                set_page_loading(true, dispatch.clone());\n                let response = api_user_info().await;\n                match response {\n                    Ok(user) =&gt; {\n                        set_page_loading(false, dispatch.clone());\n                        set_auth_user(Some(user), dispatch);\n                    }\n                    Err(e) =&gt; {\n                        set_page_loading(false, dispatch.clone());\n                        set_show_alert(e.to_string(), dispatch);\n                        navigator.push(&amp;router::Route::LoginPage);\n                    }\n                }\n            });\n        },\n        (),\n    );\n\n    html! {\n    &lt;&gt;\n      &lt;Header \/&gt;\n      &lt;section class=&quot;bg-ct-blue-600 min-h-screen pt-20&quot;&gt;\n        &lt;div class=&quot;max-w-4xl mx-auto bg-ct-dark-100 rounded-md h-[20rem] flex justify-center items-center&quot;&gt;\n          &lt;div&gt;\n            &lt;p class=&quot;text-5xl font-semibold&quot;&gt;{&quot;Profile Page&quot;}&lt;\/p&gt;\n            if let Some(user) = user {\n              &lt;div class=&quot;flex items-center gap-8&quot;&gt;\n                &lt;div&gt;\n                  &lt;img\n                      src={\n                          if user.photo.contains(&quot;default.png&quot;) {\n                              format!(&quot;http:\/\/localhost:8000\/api\/images\/{}&quot;, user.photo)\n                          } else {\n                              user.photo\n                          }\n                      }\n                      class=&quot;max-h-36&quot;\n                      alt={format!(&quot;profile photo of {}&quot;, user.name)}\n                  \/&gt;\n\n                &lt;\/div&gt;\n                &lt;div class=&quot;mt-8&quot;&gt;\n                  &lt;p class=&quot;mb-4&quot;&gt;{format!(&quot;ID: {}&quot;, user.id)}&lt;\/p&gt;\n                  &lt;p class=&quot;mb-4&quot;&gt;{format!(&quot;Name: {}&quot;, user.name)}&lt;\/p&gt;\n                  &lt;p class=&quot;mb-4&quot;&gt;{format!(&quot;Email: {}&quot;, user.email)}&lt;\/p&gt;\n                  &lt;p class=&quot;mb-4&quot;&gt;{format!(&quot;Role: {}&quot;, user.role)}&lt;\/p&gt;\n                  &lt;p class=&quot;mb-4&quot;&gt;{format!(&quot;Provider: {}&quot;, user.provider)}&lt;\/p&gt;\n                &lt;\/div&gt;\n              &lt;\/div&gt;\n            }else {\n              &lt;p class=&quot;mb-4&quot;&gt;{&quot;Loading...&quot;}&lt;\/p&gt;\n            }\n          &lt;\/div&gt;\n        &lt;\/div&gt;\n      &lt;\/section&gt;\n    &lt;\/&gt;\n    }\n}\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Home Page<\/h3>\n\n\n\n<p>Finally, we&#8217;ll create the home page component. This component is straightforward and will display a simple message to the user. To get started, create a file named <code>home_page.rs<\/code> in the <code>src\/pages<\/code> directory and add the following code to it.<\/p>\n\n\n\n<p><strong>src\/pages\/home_page.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse crate::components::header::Header;\nuse yew::prelude::*;\n\n#[function_component(HomePage)]\npub fn home_page() -&gt; Html {\n    html! {\n      &lt;&gt;\n        &lt;Header \/&gt;\n        &lt;section class=&quot;bg-ct-blue-600 min-h-screen pt-20&quot;&gt;\n            &lt;div class=&quot;max-w-4xl mx-auto bg-ct-dark-100 rounded-md h-[20rem] flex justify-center items-center&quot;&gt;\n                &lt;p class=&quot;text-3xl font-semibold&quot;&gt;{&quot;Welcome to Google OAuth2 with Yew.rs&quot;}&lt;\/p&gt;\n            &lt;\/div&gt;\n        &lt;\/section&gt;\n      &lt;\/&gt;\n    }\n}\n<\/code>\n<\/pre>\n\n\n\n<p>To enable us to define routes for the pages we just created, we need to export their respective modules. To do this, create a file named <code>mod.rs<\/code> and include the following code, which will allow us to use the page components in another module:<\/p>\n\n\n\n<p><strong>src\/components\/mod.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\npub mod home_page;\npub mod login_page;\npub mod profile_page;\npub mod register_page;\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create Routes for the Page Components<\/h2>\n\n\n\n<p>Now that we&#8217;ve built all the page components, we&#8217;re ready to create routes for them. To do this, we&#8217;ll create a <code>router.rs<\/code> file in the &#8216;<strong>src<\/strong>&#8216; directory and add the following code. This code imports the necessary modules and defines the routes for our application. Each route is associated with a specific page component, which is rendered using the &#8216;<strong>switch<\/strong>&#8216; function when the user navigates to the corresponding URL.<\/p>\n\n\n\n<p><strong>src\/router.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse yew::prelude::*;\nuse yew_router::prelude::*;\n\nuse crate::pages::{\n    home_page::HomePage, login_page::LoginPage, profile_page::ProfilePage,\n    register_page::RegisterPage,\n};\n\n#[derive(Clone, Routable, PartialEq)]\npub enum Route {\n    #[at(&quot;\/&quot;)]\n    HomePage,\n    #[at(&quot;\/register&quot;)]\n    RegisterPage,\n    #[at(&quot;\/login&quot;)]\n    LoginPage,\n    #[at(&quot;\/profile&quot;)]\n    ProfilePage,\n}\n\npub fn switch(routes: Route) -&gt; Html {\n    match routes {\n        Route::HomePage =&gt; html! {&lt;HomePage\/&gt; },\n        Route::RegisterPage =&gt; html! {&lt;RegisterPage\/&gt; },\n        Route::LoginPage =&gt; html! {&lt;LoginPage\/&gt; },\n        Route::ProfilePage =&gt; html! {&lt;ProfilePage\/&gt; },\n    }\n}\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create the App Component<\/h2>\n\n\n\n<p>In order to register the routes we defined earlier, we will use the <code>BrowserRouter<\/code> component provided by the Yew Router library. We&#8217;ll also render the <code>AlertComponent<\/code> and <code>Spinner<\/code> components in this file. Although we could put this code in the main file, it&#8217;s generally good practice to create an <strong>App<\/strong> component that handles these tasks to keep the main file clean and organized.<\/p>\n\n\n\n<p>To get started, create a new file named <code>app.rs<\/code> in the <code>src<\/code> directory and add the following code:<\/p>\n\n\n\n<p><strong>src\/app.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nuse crate::components::{\n    alert::{AlertComponent, Props as AlertProps},\n    spinner::Spinner,\n};\nuse yew::prelude::*;\nuse yew_router::prelude::*;\nuse yewdux::prelude::use_store;\n\nuse crate::router::{switch, Route};\nuse crate::store::Store;\n\n#[function_component(App)]\npub fn app() -&gt; Html {\n    let (store, _) = use_store::&lt;Store&gt;();\n    let message = store.alert_input.alert_message.clone();\n    let show_alert = store.alert_input.show_alert;\n    let is_page_loading = store.page_loading.clone();\n\n    let alert_props = AlertProps {\n        message,\n        delay_ms: 5000,\n    };\n    html! {\n        &lt;BrowserRouter&gt;\n                &lt;Switch&lt;Route&gt; render={switch} \/&gt;\n                if show_alert {\n                    &lt;AlertComponent\n                        message={alert_props.message}\n                        delay_ms={alert_props.delay_ms}\n                     \/&gt;\n                }\n                if is_page_loading {\n                    &lt;div class=&quot;pt-4 pl-2 top-[5.5rem] fixed&quot;&gt;\n                        &lt;Spinner width=&quot;1.5rem&quot; height=&quot;1.5rem&quot; color=&quot;text-ct-yellow-600&quot; \/&gt;\n                    &lt;\/div&gt;\n                }\n        &lt;\/BrowserRouter&gt;\n    }\n}\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Render the App Component in the Main File<\/h2>\n\n\n\n<p>We&#8217;re almost done! We just need to import all the modules we&#8217;ve created so far and render the <code>App<\/code> component we defined earlier. Open the <code>src\/main.rs<\/code> file and replace its content with the following code:<\/p>\n\n\n\n<p><strong>src\/main.rs<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-rust\"><code>\nmod api;\nmod app;\nmod components;\nmod pages;\nmod router;\nmod store;\nmod utils;\n\nfn main() {\n    yew::Renderer::&lt;app::App&gt;::new().render();\n}\n<\/code>\n<\/pre>\n\n\n\n<p>Congratulations! You&#8217;ve now completed the project by building a full-featured frontend application in Rust using the Yew.rs framework. With this application, users can easily sign up for an account, log in and out, and access protected routes. Additionally, users can sign in with their Google or GitHub accounts using OAuth.<\/p>\n\n\n\n<p>To get started with the application, simply ensure that the backend server is up and running. Once it is, you can then begin to interact with the frontend app to create an account, log in, and explore all of its exciting features.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How to Obtain the Google OAuth Credentials<\/h2>\n\n\n\n<p>In this section, you&#8217;ll learn how to obtain the Google OAuth2 client ID and secret from the Google Cloud Console. Here are the steps to follow:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Go to <a href=\"https:\/\/console.developers.google.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/console.developers.google.com\/<\/a> and make sure you&#8217;re signed in to your Google account.<\/li>\n\n\n\n<li>Click the dropdown menu at the top of the page to display a pop-up window. From there, you can choose an existing project or create a new one.<img loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"671\" class=\"wp-image-10054\" style=\"width: 840px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-a-project-or-create-a-new-one-on-the-Google-Cloud-API-dashboard.webp\" alt=\"select a project or create a new one on the Google Cloud API dashboard\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-a-project-or-create-a-new-one-on-the-Google-Cloud-API-dashboard.webp 840w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-a-project-or-create-a-new-one-on-the-Google-Cloud-API-dashboard-300x240.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-a-project-or-create-a-new-one-on-the-Google-Cloud-API-dashboard-768x613.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-a-project-or-create-a-new-one-on-the-Google-Cloud-API-dashboard-100x80.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-a-project-or-create-a-new-one-on-the-Google-Cloud-API-dashboard-563x450.webp 563w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><\/li>\n\n\n\n<li>To create a new project, click the &#8220;New Project&#8221; button in the top-right corner of the pop-up window. Enter a name for your project and click the &#8220;Create&#8221; button to complete the process.<br><img loading=\"lazy\" decoding=\"async\" width=\"852\" height=\"691\" class=\"wp-image-10055\" style=\"width: 852px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/create-a-new-project-on-the-google-console-api-dashboard.webp\" alt=\"create a new project on the google console api dashboard\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/create-a-new-project-on-the-google-console-api-dashboard.webp 852w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/create-a-new-project-on-the-google-console-api-dashboard-300x243.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/create-a-new-project-on-the-google-console-api-dashboard-768x623.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/create-a-new-project-on-the-google-console-api-dashboard-100x81.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/create-a-new-project-on-the-google-console-api-dashboard-555x450.webp 555w\" sizes=\"auto, (max-width: 852px) 100vw, 852px\" \/><\/li>\n\n\n\n<li>Once your project is created, click the &#8220;SELECT PROJECT&#8221; button from the notifications.<br><img loading=\"lazy\" decoding=\"async\" width=\"690\" height=\"483\" class=\"wp-image-10056\" style=\"width: 690px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-newly-created-project-from-the-notification.webp\" alt=\"click on the newly created project from the notification\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-newly-created-project-from-the-notification.webp 690w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-newly-created-project-from-the-notification-300x210.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-newly-created-project-from-the-notification-100x70.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-newly-created-project-from-the-notification-643x450.webp 643w\" sizes=\"auto, (max-width: 690px) 100vw, 690px\" \/><\/li>\n\n\n\n<li>Click the &#8220;OAuth consent screen&#8221; menu on the left sidebar. Choose &#8220;External&#8221; as the &#8220;User Type&#8221; and click on the &#8220;CREATE&#8221; button.<br><img loading=\"lazy\" decoding=\"async\" width=\"919\" height=\"669\" class=\"wp-image-10057\" style=\"width: 919px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-external-under-the-user-type-and-click-on-create.webp\" alt=\"select external under the user type and click on create\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-external-under-the-user-type-and-click-on-create.webp 919w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-external-under-the-user-type-and-click-on-create-300x218.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-external-under-the-user-type-and-click-on-create-768x559.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-external-under-the-user-type-and-click-on-create-100x73.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-external-under-the-user-type-and-click-on-create-618x450.webp 618w\" sizes=\"auto, (max-width: 919px) 100vw, 919px\" \/><\/li>\n\n\n\n<li>On the &#8220;Edit app registration&#8221; screen, go to the &#8220;App information&#8221; section and fill in the required details, including a logo for the consent screen.<br><img loading=\"lazy\" decoding=\"async\" width=\"951\" height=\"915\" class=\"wp-image-10059\" style=\"width: 951px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-1.webp\" alt=\"provide the consent screen credentials part 1\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-1.webp 951w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-1-300x289.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-1-768x739.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-1-100x96.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-1-468x450.webp 468w\" sizes=\"auto, (max-width: 951px) 100vw, 951px\" \/><br>Under the &#8220;App domain&#8221; section, provide links to your homepage, privacy policy, and terms of service pages. Input your email address under the &#8220;Developer contact information&#8221; section and click on the &#8220;SAVE AND CONTINUE&#8221; button.<br><img loading=\"lazy\" decoding=\"async\" width=\"950\" height=\"977\" class=\"wp-image-10058\" style=\"width: 950px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-2.webp\" alt=\"provide the consent screen credentials part 2\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-2.webp 950w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-2-292x300.webp 292w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-2-768x790.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-2-97x100.webp 97w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/provide-the-consent-screen-credentials-part-2-438x450.webp 438w\" sizes=\"auto, (max-width: 950px) 100vw, 950px\" \/><\/li>\n\n\n\n<li>On the &#8220;Scopes&#8221; screen, click on the &#8220;ADD OR REMOVE SCOPES&#8221; button, select <code>...\/auth\/userinfo.email<\/code> and <code>...\/auth\/userinfo.profile<\/code> from the list of options, and then click on the &#8220;UPDATE&#8221; button. Scroll down and click the &#8220;SAVE AND CONTINUE&#8221; button.<br><img loading=\"lazy\" decoding=\"async\" width=\"813\" height=\"432\" class=\"wp-image-10061\" style=\"width: 813px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-the-scopes.webp\" alt=\"select the scopes\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-the-scopes.webp 813w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-the-scopes-300x159.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-the-scopes-768x408.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-the-scopes-100x53.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-the-scopes-700x372.webp 700w\" sizes=\"auto, (max-width: 813px) 100vw, 813px\" \/><\/li>\n\n\n\n<li>On the &#8220;Test users&#8221; screen, add the email addresses of Google accounts that will be authorized to test your application while it is in sandbox mode. Click the &#8220;ADD USERS&#8221; button and input the email addresses. Click the &#8220;SAVE AND CONTINUE&#8221; button to proceed.<img loading=\"lazy\" decoding=\"async\" width=\"811\" height=\"357\" class=\"wp-image-10062\" style=\"width: 811px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/add-the-test-user.webp\" alt=\"add the test user\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/add-the-test-user.webp 811w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/add-the-test-user-300x132.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/add-the-test-user-768x338.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/add-the-test-user-100x44.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/add-the-test-user-700x308.webp 700w\" sizes=\"auto, (max-width: 811px) 100vw, 811px\" \/><\/li>\n\n\n\n<li>Click on the &#8220;Credentials&#8221; option in the left sidebar. Select the &#8220;CREATE CREDENTIALS&#8221; button and choose &#8220;OAuth client ID&#8221; from the list of options provided.<br><img loading=\"lazy\" decoding=\"async\" width=\"589\" height=\"363\" class=\"wp-image-10063\" style=\"width: 589px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-oauth-client-ID.webp\" alt=\"select oauth client ID\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-oauth-client-ID.webp 589w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-oauth-client-ID-300x185.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/select-oauth-client-ID-100x62.webp 100w\" sizes=\"auto, (max-width: 589px) 100vw, 589px\" \/><\/li>\n\n\n\n<li>Choose &#8220;Web application&#8221; as the application type, and input a name for your app. Specify the authorized redirect URI as <code>http:\/\/localhost:8000\/api\/sessions\/oauth\/google<\/code> and click the &#8220;Create&#8221; button.<br><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"836\" class=\"wp-image-12562\" style=\"width: 800px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/set-up-the-Google-OAuth-app-for-the-Rust-API-Project.webp\" alt=\"set up the Google OAuth app for the Rust API Project\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/set-up-the-Google-OAuth-app-for-the-Rust-API-Project.webp 819w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/set-up-the-Google-OAuth-app-for-the-Rust-API-Project-287x300.webp 287w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/set-up-the-Google-OAuth-app-for-the-Rust-API-Project-768x803.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/set-up-the-Google-OAuth-app-for-the-Rust-API-Project-96x100.webp 96w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/set-up-the-Google-OAuth-app-for-the-Rust-API-Project-431x450.webp 431w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><br><br>After the client ID has been created, copy the client ID and secret from the &#8220;Credentials&#8221; page and add them to the <code>.env<\/code> file. Here&#8217;s an example <code>.env<\/code> file to guide you.<\/li>\n<\/ol>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nGOOGLE_OAUTH_CLIENT_ID=\nGOOGLE_OAUTH_CLIENT_SECRET=\nGOOGLE_OAUTH_REDIRECT_URL=http:\/\/localhost:8000\/api\/sessions\/oauth\/google\n\nGITHUB_OAUTH_CLIENT_ID=\nGITHUB_OAUTH_CLIENT_SECRET=\nGITHUB_OAUTH_REDIRECT_URL=http:\/\/localhost:8000\/api\/sessions\/oauth\/github\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">How to Obtain the GitHub OAuth Credentials<\/h2>\n\n\n\n<p>Follow these steps to register a GitHub OAuth app and obtain the Client ID and Secret.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Start by signing into your GitHub account. Click on your profile picture at the top right corner and select &#8220;Settings&#8221; from the dropdown menu. You&#8217;ll be taken to the GitHub Developer Settings page.<br><img loading=\"lazy\" decoding=\"async\" width=\"336\" height=\"722\" class=\"wp-image-10100\" style=\"width: 336px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-profile-photo-icon-to-display-a-dropdown.webp\" alt=\"click on the profile photo icon to display a dropdown\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-profile-photo-icon-to-display-a-dropdown.webp 336w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-profile-photo-icon-to-display-a-dropdown-140x300.webp 140w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-profile-photo-icon-to-display-a-dropdown-47x100.webp 47w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-profile-photo-icon-to-display-a-dropdown-209x450.webp 209w\" sizes=\"auto, (max-width: 336px) 100vw, 336px\" \/><\/li>\n\n\n\n<li>Scroll down to find the &#8220;Developer settings&#8221; section and click on it.<br><img loading=\"lazy\" decoding=\"async\" width=\"866\" height=\"683\" class=\"wp-image-10101\" style=\"width: 866px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-developer-settings-menu-on-the-profile-settings-page.webp\" alt=\"click on the developer settings menu on the profile settings page\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-developer-settings-menu-on-the-profile-settings-page.webp 866w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-developer-settings-menu-on-the-profile-settings-page-300x237.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-developer-settings-menu-on-the-profile-settings-page-768x606.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-developer-settings-menu-on-the-profile-settings-page-100x79.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/click-on-the-developer-settings-menu-on-the-profile-settings-page-571x450.webp 571w\" sizes=\"auto, (max-width: 866px) 100vw, 866px\" \/><\/li>\n\n\n\n<li>On the Developer settings page, look for &#8220;OAuth Apps&#8221; and click on it. Under the &#8220;OAuth Apps&#8221; section, click on the &#8220;New OAuth App&#8221; button.<\/li>\n\n\n\n<li>Fill in the &#8220;Application name&#8221; and &#8220;Homepage URL&#8221; input fields with the appropriate name and URL for your app. For the &#8220;Authorization callback URL&#8221;, enter the URL where GitHub will redirect users after they&#8217;ve authorized the app.<br><img loading=\"lazy\" decoding=\"async\" width=\"600\" height=\"696\" class=\"wp-image-12563\" style=\"width: 600px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/set-up-the-GitHub-OAuth-app-for-the-Rust-API-Project.webp\" alt=\"set up the GitHub OAuth app for the Rust API Project\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/set-up-the-GitHub-OAuth-app-for-the-Rust-API-Project.webp 638w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/set-up-the-GitHub-OAuth-app-for-the-Rust-API-Project-259x300.webp 259w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/set-up-the-GitHub-OAuth-app-for-the-Rust-API-Project-86x100.webp 86w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/05\/set-up-the-GitHub-OAuth-app-for-the-Rust-API-Project-388x450.webp 388w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><br>In this example, you need to enter <code>http:\/\/localhost:8000\/api\/sessions\/oauth\/github<\/code> as the redirect URL. Once you&#8217;re finished, hit the &#8220;Register application&#8221; button to create the OAuth App.<\/li>\n\n\n\n<li>Congrats, your application has been created! You&#8217;ll now be taken to the application details page where you can access the &#8220;Client ID&#8221; and generate the &#8220;Client Secret&#8221; keys.<br><br>To generate the OAuth client secret, click on the &#8220;Generate a new client secret&#8221; button. GitHub will then prompt you to confirm your identity before the client secret is generated.<br><br><img loading=\"lazy\" decoding=\"async\" width=\"1130\" height=\"911\" class=\"wp-image-10105\" style=\"width: 1130px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/the-oauth-client-secret-will-be-generated.webp\" alt=\"the oauth client secret will be generated\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/the-oauth-client-secret-will-be-generated.webp 1130w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/the-oauth-client-secret-will-be-generated-300x242.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/the-oauth-client-secret-will-be-generated-1024x826.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/the-oauth-client-secret-will-be-generated-768x619.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/the-oauth-client-secret-will-be-generated-100x81.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2023\/01\/the-oauth-client-secret-will-be-generated-558x450.webp 558w\" sizes=\"auto, (max-width: 1130px) 100vw, 1130px\" \/><\/li>\n\n\n\n<li>With the GitHub OAuth client secret now generated, it&#8217;s time to add it, along with the client ID, to your <code>.env<\/code> file. Check out this example <code>.env<\/code> file to guide you.<\/li>\n<\/ol>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nGOOGLE_OAUTH_CLIENT_ID=\nGOOGLE_OAUTH_CLIENT_SECRET=\nGOOGLE_OAUTH_REDIRECT_URL=http:\/\/localhost:8000\/api\/sessions\/oauth\/google\n\nGITHUB_OAUTH_CLIENT_ID=\nGITHUB_OAUTH_CLIENT_SECRET=\nGITHUB_OAUTH_REDIRECT_URL=http:\/\/localhost:8000\/api\/sessions\/oauth\/github\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>In conclusion, we have successfully built a frontend web application using Rust and the Yew framework that supports credential authentication and OAuth login through Google and GitHub. Additionally, we learned how to register an OAuth application on these platforms, which is a valuable skill in itself. <\/p>\n\n\n\n<p>I hope this guide has been helpful and informative for you. If you have any questions or feedback, please don&#8217;t hesitate to leave a comment. Thank you for reading!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article, you will learn how to implement OAuth for Google and GitHub in a Rust frontend application using the Yew.rs framework. Additionally, I&#8230;<\/p>\n","protected":false},"author":1,"featured_media":11415,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[87],"tags":[88],"class_list":["post-11397","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-rust","tag-rust"],"acf":[],"_links":{"self":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/11397","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/comments?post=11397"}],"version-history":[{"count":3,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/11397\/revisions"}],"predecessor-version":[{"id":12564,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/11397\/revisions\/12564"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/11415"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=11397"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=11397"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=11397"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}