> For the complete documentation index, see [llms.txt](/docs/llms.txt)

# Rust with Actix | FusionAuth Docs

Quickstart integration of a Rust Actix web application with FusionAuth.

# Rust with Actix

In this quickstart, you are going to build an application with Rust and Actix and integrate it with FusionAuth. You'll be building it for [ChangeBank](https://www.youtube.com/watch?v=CXDxNCzUspM), a global leader in converting dollars into coins. It'll have areas reserved for users who have logged in as well as public facing sections.

The Docker Compose file and source code for a complete application are available at [https://github.com/FusionAuth/fusionauth-quickstart-rust-actix-web](https://github.com/FusionAuth/fusionauth-quickstart-rust-actix-web).

While this article uses , the OAuth2 library can also be used in your preferred framework, such as Rocket or Axum.

## Prerequisites[#](#prerequisites)

For this quickstart, you'll need:

*   [Docker](https://www.docker.com) version 20 or later, which is the quickest way to start FusionAuth. (There are [other ways](/docs/get-started/download-and-install/).)
*   and Cargo version 1.7 or later.
*   You may also need to install `pkg-config` and `libssl-dev` if they are not already installed on your system.

## General Architecture[#](#general-architecture)

Here's a typical application login flow before FusionAuth:

```mermaid
sequenceDiagram
    participant User
    participant App as Application

    User ->> App : View Homepage
    User ->> App : Click Login Link
    App ->> User : Show Login Form
    User ->> App : Fill Out and Submit Login Form
    App ->> App : Authenticates User
    App ->> User : Display User's Account or Other Info
```

Request flow during login before FusionAuth

And here's the same application login flow after introducing FusionAuth:

```mermaid
sequenceDiagram
    participant User
    participant App as Application
    participant FusionAuth

    User ->> App : View Homepage
    User ->> App : Click Login Link (to FusionAuth)
    User ->> FusionAuth : View Login Form
    FusionAuth ->> User : Show Login Form
    User ->> FusionAuth : Fill Out and Submit Login Form
    FusionAuth ->> FusionAuth : Authenticates User
    FusionAuth ->> User: Go to Redirect URI
    User ->> App: Request the Redirect URI
    App ->> FusionAuth : Is User Authenticated?
    FusionAuth ->> App : User is Authenticated
    App ->> User : Display User's Account or Other Info
```

Request flow during login after FusionAuth

In general, you are introducing FusionAuth in order to normalize and consolidate user data. This helps make sure it is consistent and up-to-date as well as offloading your login security and functionality to FusionAuth.

## Getting Started[#](#getting-started)

Start with getting FusionAuth up and running and creating a new application.

### Clone The Code[#](#clone-the-code)

First, grab the code from the repository and change to that folder.

```
git clone https://github.com/FusionAuth/fusionauth-quickstart-rust-actix-web.git
cd fusionauth-quickstart-rust-actix-web
mkdir your-application
```

All shell commands in this guide can be entered in a terminal in this folder. On Windows, you need to replace forward slashes with backslashes in paths.

All the files you'll create in this guide already exist in the `complete-application` subfolder, if you prefer to copy them to `your-application`.

### Run FusionAuth Via Docker[#](#run-fusionauth-via-docker)

You'll find a Docker Compose file (`docker-compose.yml`) and an environment variables configuration file (`.env`) in the root directory of the repo.

Assuming you have Docker installed, you can stand up FusionAuth on your machine with the following.

```
docker compose up -d
```

Here you are using a bootstrapping feature of FusionAuth called [Kickstart](/docs/get-started/download-and-install/development/kickstart). When FusionAuth comes up for the first time, it will look at the `kickstart/kickstart.json` file and configure FusionAuth to your specified state.

If you ever want to reset the FusionAuth application, you need to delete the volumes created by Docker Compose by executing `docker compose down -v`, then re-run `docker compose up -d`.

FusionAuth will be initially configured with these settings:

*   Your client Id is `E9FDB985-9173-4E01-9D73-AC2D60D1DC8E`.
*   Your client secret is `super-secret-secret-that-should-be-regenerated-for-production`.
*   Your example username is `richard@example.com` and the password is `password`.
*   Your admin username is `admin@example.com` and the password is `password`.
*   The base URL of FusionAuth is `http://localhost:9011/`.

You can log in to the [FusionAuth admin UI](http://localhost:9011/admin) and look around if you want to, but with Docker and Kickstart, everything will already be configured correctly.

If you want to see where the FusionAuth values came from, they can be found in the [FusionAuth app](http://localhost:9011/admin). The tenant Id is found on the Tenants page. To see the Client Id and Client Secret, go to the Applications page and click the `View` icon under the actions for the ChangeBank application. You'll find the Client Id and Client Secret values in the `OAuth configuration` section.

The `.env` file contains passwords. In a real application, always add this file to your `.gitignore` file and never commit secrets to version control.

### The Basic Application[#](#the-basic--application)

While this guide builds a new project, you can use the same method to integrate your existing project with FusionAuth.

If you only want to run the application and not create your own, there is a completed version in the `complete-application` directory. You can use the following commands to get it up and running.

```
cd complete-application
cargo run
```

View the application at [http://localhost:9012](http://localhost:9012).

From here on, you'll work in the `your-application` directory. Install the dependencies for the web server with the code below.

```
cd your-application
cargo init
cargo add actix-web@4 
cargo add actix-files@0.6.2 
cargo add actix-session@0.8.0 --features cookie-session 
cargo add dotenv@0.15.0 
cargo add handlebars@4.5 --features dir_source 
cargo add oauth2@4.4.2 
cargo add reqwest@0.11 --features json
cargo add serde@1.0 --features derive
```

Add the following code to your `src/main.rs` file.

```
use actix_web::{get, post, web, App, HttpResponse, HttpServer}; // web server
use actix_files as fs; // static image files
use actix_session::{Session, SessionMiddleware, storage::CookieSessionStore, config::CookieContentSecurity}; // store auth info in browser cookies
use actix_web::cookie::{Key, SameSite};
use handlebars::Handlebars; // html templates
use std::collections::HashMap; // pass data to templates
use dotenv::dotenv; // load .env file
mod auth;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    dotenv().ok();
    let handlebars_ref = setup_handlebars().await;
    let key = Key::generate();
    HttpServer::new(move || {
        App::new()
            .wrap(SessionMiddleware::builder(CookieSessionStore::default(), key.clone())
                    .cookie_content_security(CookieContentSecurity::Private)
                    .cookie_same_site(SameSite::Lax)
                    .build())
            .service(account)
            .service(change_get)
            .service(change_post)
            .service(index)
            .service(auth::login)
            .service(auth::logout)
            .service(auth::callback)
            .service(fs::Files::new("/static", "static").show_files_listing())
            .app_data(handlebars_ref.clone())
    })
    .bind(("127.0.0.1", 9012))?
    .run()
    .await
}

async fn setup_handlebars() -> web::Data<Handlebars<'static>> {
    let mut handlebars = Handlebars::new();
    handlebars
        .register_templates_directory(".html", "./templates")
        .unwrap();
    web::Data::new(handlebars)
}

#[get("/")]
async fn index(hb: web::Data<Handlebars<'_>>, session: Session) -> HttpResponse {
    if let Ok(Some(_)) = session.get::<String>("email") {
        return HttpResponse::Found().append_header(("Location", "/account")).finish();
    }
    let body = hb.render("index", &{}).unwrap();
    HttpResponse::Ok().body(body)
}

#[get("/account")]
async fn account(hb: web::Data<Handlebars<'_>>, session: Session) -> HttpResponse {
    if let Ok(None) | Err(_) = session.get::<String>("email") {
        return HttpResponse::Found().append_header(("Location", "/")).finish();
    }
    let mut data = HashMap::new();
    data.insert("email", session.get::<String>("email").unwrap());
    let body = hb.render("account", &data).unwrap();
    HttpResponse::Ok().body(body)
}

#[get("/change")]
async fn change_get(hb: web::Data<Handlebars<'_>>, session: Session) -> HttpResponse {
    if let Ok(None) | Err(_) = session.get::<String>("email") {
        return HttpResponse::Found().append_header(("Location", "/")).finish();
    }
    let mut data = HashMap::<&str, String>::new();
    data.insert("email", session.get::<String>("email").unwrap().unwrap());
    data.insert("isGetRequest", "true".to_string());
    let body = hb.render("change", &data).unwrap();
    HttpResponse::Ok().body(body)
}

#[post("/change")]
async fn change_post(hb: web::Data<Handlebars<'_>>, session: Session, form: web::Form<HashMap<String, String>>) -> HttpResponse {
    if let Ok(None) | Err(_) = session.get::<String>("email") {
        return HttpResponse::Found().append_header(("Location", "/")).finish();
    }
    let mut data = HashMap::<&str, String>::new();
    data.insert("email", session.get::<String>("email").unwrap().unwrap());
    data.insert("isGetRequest", "false".to_string());
    if let Some(amount) = form.get("amount") {
        calculate_change(amount, &mut data);
    }
    else {
        data.insert("isError", "true".to_string());
    }
    let body = hb.render("change", &data).unwrap();
    HttpResponse::Ok().body(body)
}

fn calculate_change(amount: &str, state: &mut HashMap::<&str, String>) -> () {
    let total = match amount.parse::<f64>() {
        Ok(t) => t,
        Err(_) => {
            state.insert("isError", "true".to_string());
            return;
        }
    };
    let rounded_total = (total * 100.0).floor() / 100.0;

    state.insert("isError", (!amount.chars().all(|c| c.is_digit(10) || c == '.')).to_string());
    state.insert("total", format!("{:.2}", rounded_total));

    let nickels = (rounded_total / 0.05).floor().abs();
    state.insert("nickels", format!("{}", nickels));

    let pennies = ((rounded_total - (0.05 * nickels)) / 0.01).round().abs();
    state.insert("pennies", format!("{}", pennies));
}
```

The `main` function configures the web server, adds routes to it, and starts it. The function also:

*   Uses `dotenv` to allow you to call any values from the `.env` file later in the application.
*   Uses private same-site cookies to store the user's email when logged in. does not provide an anti-CSRF token as they [believe same-site cookies render it unnecessary](https://github.com/actix/actix-web/issues/147).
*   Adds routes from the main file to the server, and some authentication routes, which you'll add in the next section.
*   Enables the Handlebars library to provide HTML templates.

The remainder of this main file is three routes: index, account, and change. They each check if the user's email is in the HTTP request cookie (meaning the user is logged in), and display an HTML template. The account and change routes send the user's email to the template.

The change route is more complicated. It has two versions: one for GET and one for POST. The POST version checks the request's form to extract a dollar amount, then calls `calculate_change` to convert the dollars to nickels and pennies, and returns them in the state to the Handlebars template.

## Authentication[#](#authentication)

Authentication in is managed by [OAuth2](https://docs.rs/oauth2/latest/oauth2/).

Create the file `.env` in the `your-application` directory and insert the following lines.

```
FUSIONAUTH_CLIENT_ID="E9FDB985-9173-4E01-9D73-AC2D60D1DC8E"
FUSIONAUTH_CLIENT_SECRET="super-secret-secret-that-should-be-regenerated-for-production"
FUSIONAUTH_URL="http://localhost:9011"
FUSIONAUTH_REDIRECT_URL="http://localhost:9012/callback"
```

This tells where to find and connect to FusionAuth.

Authentication is handled by `src/auth.rs`. Create that file now and insert the following code.

```
use actix_web::{get, web, Error, HttpResponse, Responder};
use actix_session::{Session};
use std::env;
use oauth2::{
    AuthorizationCode,
    AuthUrl,
    ClientId,
    ClientSecret,
    CsrfToken,
    PkceCodeChallenge,
    PkceCodeVerifier,
    RedirectUrl,
    Scope,
    TokenResponse,
    TokenUrl
};
use oauth2::basic::BasicClient;
use oauth2::reqwest::async_http_client;
use reqwest;
use serde::Deserialize;

#[get("/logout")]
async fn logout(session: Session) -> impl Responder {
    let _ = session.remove("email");
    HttpResponse::Found().append_header(("Location", "/")).finish()
}

#[get("/login")]
async fn login(session: Session) -> impl Responder {
    let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
    let (auth_url, csrf_token) = get_oauth_client()
        .authorize_url(CsrfToken::new_random)
        .add_scope(Scope::new("openid".to_string()))
        .add_scope(Scope::new("email".to_string()))
        .add_scope(Scope::new("profile".to_string()))
        .set_pkce_challenge(pkce_challenge)
        .url();
    let _ = session.insert("csrf_token", csrf_token);
    let _ = session.insert("pkce_verifier", pkce_verifier);
    HttpResponse::Found().append_header(("Location", auth_url.to_string())).finish()
}

#[get("/callback")]
async fn callback(params: web::Query<AuthCallbackParams>, session: Session) ->  Result<HttpResponse, Error> {
    // confirm pkce match
    let received_state = &params.state;
    if let Ok(saved_state) = session.get::<String>("csrf_token") {
        if saved_state != Some(received_state.clone()) {
            return Ok(HttpResponse::BadRequest().body("PKCE state mismatch"));
        }
    }
    else {
        return Ok(HttpResponse::InternalServerError().body("Session error"));
    }

    // get access token
    let pkce_verifier = session.get::<String>("pkce_verifier").unwrap().unwrap();
    let token_result = match get_oauth_client()
        .exchange_code(AuthorizationCode::new(params.code.clone()))
        .set_pkce_verifier(PkceCodeVerifier::new(pkce_verifier))
        .request_async(async_http_client)
        .await {
            Ok(result) => result,
            Err(e) => {
                println!("{:#?}", e);
                return Ok(HttpResponse::InternalServerError().body("Error during token exchange"));
            }
        };

    // get email
    let client = reqwest::Client::new();
    let user_info_response = match client
        .get(format!("{}/oauth2/userinfo", env::var("FUSIONAUTH_URL").expect("Missing FUSIONAUTH_URL")))
        .bearer_auth(token_result.access_token().secret())
        .send()
        .await {
            Ok(result) => result,
            Err(e) => {
                println!("{:#?}", e);
                return Ok(HttpResponse::InternalServerError().body("Error during get email"));
            }
        };
    if user_info_response.status().is_success() {
        let user_info = match user_info_response.json::<UserInfo>().await {
            Ok(result) => result,
            Err(e) => {
                println!("{:#?}", e);
                return Ok(HttpResponse::InternalServerError().body("Error during get email2"));
            }
        };
        let _ = session.insert("email", user_info.email.clone());
    }
    else {
        println!("{:#?}", user_info_response.error_for_status().unwrap_err());
        return Ok(HttpResponse::InternalServerError().body("Error during get email3"));
    }
    Ok(HttpResponse::Found().append_header(("Location", "/account")).finish())
}

fn get_oauth_client() -> BasicClient {
    BasicClient::new(
        ClientId::new(env::var("FUSIONAUTH_CLIENT_ID").expect("Missing FUSIONAUTH_CLIENT_ID")),
        Some(ClientSecret::new(env::var("FUSIONAUTH_CLIENT_SECRET").expect("Missing FUSIONAUTH_CLIENT_SECRET"))),
        AuthUrl::new(env::var("FUSIONAUTH_URL").expect("Missing FUSIONAUTH_URL") + "/oauth2/authorize").expect("Invalid AuthUrl"),
        Some(TokenUrl::new(env::var("FUSIONAUTH_URL").expect("Missing FUSIONAUTH_URL") + "/oauth2/token").expect("Invalid TokenUrl"))
    )
    .set_redirect_uri(RedirectUrl::new(env::var("FUSIONAUTH_REDIRECT_URL").expect("Missing FUSIONAUTH_REDIRECT_URL")).expect("Invalid RedirectUrl"))
}

#[derive(Deserialize)]
struct AuthCallbackParams {
    state: String,
    code: String,
}

#[derive(Deserialize)]
struct UserInfo {
    email: String,
}
```

This code has three routes: `login`, `logout`, and `callback`.

*   `logout` clears the user's session and returns them to the home page.
*   `login` uses `get_oauth_client` to read your variables from the `.env` file and get a new client that constructs a URL to call FusionAuth, then redirects the user to that URL.
*   `callback` does most of the work. The user is returned to `callback` after logging in with FusionAuth. The function checks that the PKCE challenge is correct, retrieves the access token, and makes a final call to FusionAuth to get the user's email, which it stores in the session. Now the application considers the user logged in.

automatically links the user's session to their browser by returning a cookie for the site, which is then included in every subsequent request.

Now that `callback` has set a cookie, you can see how authentication on the other pages is tested. The application:

*   Sends the user to the account page if they already have a login cookie on the home page.
*   Sends the user to the home page if they are not logged in on the account page.

## Customization[#](#customization)

With authentication done, the last task is to create example pages that a user can browse.

### CSS And HTML[#](#css-and-html)

Create a `static` directory within `your-application` directory.

```
mkdir static
```

Copy images from the example app.

```
cp ../complete-application/static/money.jpg static/money.jpg
cp ../complete-application/static/changebank.svg static/changebank.svg
```

Create a stylesheet file `static/changebank.css` and add the following code to it.

```
h1 {
  color: #096324;
}

h3 {
  color: #096324;
  margin-top: 20px;
  margin-bottom: 40px;
}

a {
  color: #096324;
}

p {
  font-size: 18px;
}

.header-email {
  color: #096324;
  margin-right: 20px;
}

.fine-print {
  font-size: 16px;
}

body {
  font-family: sans-serif;
  padding: 0px;
  margin: 0px;
}

.h-row {
  display: flex;
  align-items: center;
}

#page-container {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
}

#page-header {
  flex: 0;
  display: flex;
  flex-direction: column;
}

#logo-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
}

.menu-bar {
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  height: 35px;
  padding: 15px 50px 15px 30px;
  background-color: #096324;
  font-size: 20px;
}

.menu-link {
  font-weight: 600;
  color: #FFFFFF;
  margin-left: 40px;
}

.menu-link {
  font-weight: 600;
  color: #FFFFFF;
  margin-left: 40px;
}

.inactive {
  text-decoration-line: none;
}

.button-lg {
  width: 150px;
  height: 30px;
  background-color: #096324;
  color: #FFFFFF;
  font-size: 16px;
  font-weight: 700;
  border-radius: 10px;
  text-align: center;
  padding-top: 10px;
  text-decoration-line: none;
}

.column-container {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

.content-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 60px 20px 20px 40px;
}

.balance {
  font-size: 50px;
  font-weight: 800;
}

.change-label {
  font-size: 20px;
  margin-right: 5px;
}

.change-input {
  font-size: 20px;
  height: 40px;
  text-align: end;
  padding-right: 10px;
}

.change-submit {
  font-size: 15px;
  height: 40px;
  margin-left: 15px;
  border-radius: 5px;
}

.change-message {
  font-size: 20px;
  margin-bottom: 15px;
}

.error-message {
  font-size: 20px;
  color: #FF0000;
  margin-bottom: 15px;
}

.app-container {
  flex: 0;
  min-width: 440px;
  display: flex;
  flex-direction: column;
  margin-top: 40px;
  margin-left: 80px;
}

.change-container {
  flex: 1;
}
```

Next, create a `templates` directory within `your-application`. Create three pages inside the `templates` directory. First create the home page, `index.html`, and paste the following code into it.

```
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>FusionAuth OpenID and PKCE example</title>
  <link rel="stylesheet" href="static/changebank.css">
</head>
<body>
  <div id="page-container">
    <div id="page-header">
      <div id="logo-header">
        <a href="/">
          <img src="static/changebank.svg"  alt="logo"/>
        </a>
        <a class="button-lg" href="login">Login</a>
      </div>

      <div id="menu-bar" class="menu-bar">
        <a class="menu-link">About</a>
        <a class="menu-link">Services</a>
        <a class="menu-link">Products</a>
        <a class="menu-link" style="text-decoration-line: underline;">Home</a>
      </div>
    </div>

    <div style="flex: 1;">
      <div class="column-container">
        <div class="content-container">
          <div style="margin-bottom: 100px;">
            <h1>Welcome to Changebank</h1>
            <p>To get started, <a href="login">log in or create a new account</a>.</p>
          </div>
        </div>
        <div style="flex: 0;">
          <img src="static/money.jpg" style="max-width: 800px;" alt="coins"/>
        </div>
      </div>
    </div>
  </div>
</body>
</html>
```

The index page contains nothing to note except a link to the login page `<a href="/login">`.

Next, create an `account.html` page and paste the following code into it.

```
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>FusionAuth OpenID and PKCE example</title>
  <link rel="stylesheet" href="static/changebank.css">
</head>
<body>
  <div id="page-container">
    <div id="page-header">
      <div id="logo-header">
        <a href="/">
          <img src="static/changebank.svg"  alt="logo"/>
        </a>
        <div class="h-row">
          <p class="header-email">{{email}}</p>
          <a class="button-lg" href="/logout" onclick="">Logout</a>
        </div>
      </div>

      <div id="menu-bar" class="menu-bar">
        <a class="menu-link inactive" href="/change">Make Change</a>
        <a class="menu-link" href="/account">Account</a>
      </div>
    </div>

    <div style="flex: 1;">
      <div class="column-container">
        <div class="app-container">
          <h3>Your balance</h3>
          <div class="balance">$0.00</div>
        </div>
      </div>
    </div>
</body>
</html>
```

The account page displays the user's email from FusionAuth with `<p class="header-email">{{email}}</p>`.

The account page is only visible to logged in users. If a session email is not found, the user is redirected to login.

Finally, create a `change.html` page and paste the following code into it.

```
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>FusionAuth OpenID and PKCE example</title>
  <link rel="stylesheet" href="static/changebank.css">
</head>
<body>
  <div id="page-container">
    <div id="page-header">
      <div id="logo-header">
        <a href="/">
          <img src="static/changebank.svg" alt="logo"/>
        </a>
        <div class="h-row">
          <p class="header-email">{{email}}</p>
          <a class="button-lg" href="/logout">Logout</a>
        </div>
      </div>

      <div id="menu-bar" class="menu-bar">
        <a class="menu-link" href="/change">Make Change</a>
        <a class="menu-link inactive" href="/account">Account</a>
      </div>
    </div>

    <div style="flex: 1;">
      <div class="column-container">
        <div class="app-container change-container">
          <h3>We Make Change</h3>


<!-- GET REQUEST ------------------------------------------------>
          {{#if (eq isGetRequest "true")}}
            <div class="change-message">Please enter a dollar amount:</div>
            <form method="post" action="change">
              <div class="h-row">
                <div class="change-label">Amount in USD: $</div>
                <input class="change-input" name="amount" value="" />
                <input class="change-submit" type="submit" value="Make Change" />
              </div>
            </form>
<!-- POST REQUEST ----------------------------------------------->
          {{else}}
          {{#if (eq isError "true")}}
              <div class="error-message">Please enter a dollar amount:</div>
            {{else}}
              <div class="change-message">
                We can make change for {{total}} with {{nickels}} nickels and {{pennies}} pennies!
              </div>
            {{/if}}
            <form method="post" action="change">
              <div class="h-row">
                <div class="change-label">Amount in USD: $</div>
                <input class="change-input" name="amount" value="{{amount}}" />
                <input class="change-submit" type="submit" value="Make Change" />
              </div>
            </form>
          {{/if}}
        </div>
      </div>
    </div>
  </div>
</body>
</html>
```

The HTML at the bottom of the file displays a blank form when the page first loads (GET) or the result of the calculation when returning (POST).

## Run The Application[#](#run-the-application)

Run your application.

```
cargo run
```

Browse to the app at [http://localhost:9012](http://localhost:9012). Log in using `richard@example.com` and `password`. The change page allows you to enter a number. If you don't log in, you won't be able to access the change or account pages.

## Next Steps[#](#next-steps)

This quickstart is a great way to get a proof of concept up and running quickly, but to run your application in production, there are some things you're going to want to do.

### FusionAuth Customization

FusionAuth gives you the ability to customize just about everything to do with the user's experience and the integration of your application. This includes:

*   [Hosted pages](/docs/customize/look-and-feel/) such as login, registration, email verification, and many more.
*   [Email templates](/docs/customize/email-and-messages/email-templates).
*   [User data and custom claims in access token JWTs](/articles/tokens/jwt-components-explained).

### Security

*   You may want to customize the [token expiration times and policies](/docs/lifecycle/authenticate-users/oauth/) in FusionAuth.
*   Choose [password rules](/docs/get-started/core-concepts/tenants#password) and a [hashing algorithm](/docs/reference/password-hashes) that meet your security needs.

### Tenant and Application Management

*   Model your application topology using [Applications](/docs/get-started/core-concepts/applications), [Roles](/docs/get-started/core-concepts/roles), [Groups](/docs/get-started/core-concepts/groups), [Entities](/docs/get-started/core-concepts/groups), and more.
*   Set up [MFA](/docs/lifecycle/authenticate-users/multi-factor-authentication), [Social login](/docs/lifecycle/authenticate-users/identity-providers/), or [SAML](/docs/lifecycle/authenticate-users/identity-providers/overview-samlv2) integrations.
*   Integrate with external systems using [Webhooks](/docs/extend/events-and-webhooks/), [SCIM](/docs/lifecycle/migrate-users/scim), and [Lambdas](/docs/extend/code/lambdas/).

### Authentication[#](#-authentication)

*   [Rust OAuth provider](https://docs.rs/oauth2/latest/oauth2/)
*   [Actix](https://actix.rs/)

## Troubleshooting[#](#troubleshooting)

*   I get "This site can't be reached localhost refused to connect" when I click the login button.

Ensure FusionAuth is running in the Docker container. You should be able to log in as the admin user `admin@example.com` with the password `password` at [http://localhost:9011/admin](http://localhost:9011/admin).

*   It still doesn't work.

You can always pull down a complete running application and compare what's different.

```
git clone https://github.com/FusionAuth/fusionauth-quickstart-rust-actix-web.git
cd fusionauth-quickstart-rust-actix-web
docker compose up
cd complete-application
cargo run
```

Browse to the app at [http://localhost:9012](http://localhost:9012).