Authentication system using rust (actix-web) and sveltekit - User Registration
Tags: webdev, rust, tutorial, html
Published: 2023-04-24 17:35:08

## Introduction

Having set up our store and database of choice, it's now time to put them to work. This article does exactly that. We'll familiarize ourselves with more awesome crates in the Rust ecosystem to generate and verify secured tokens, hash and verify passwords, send multipart emails with a direct SMTP server, and feel more at home with some other actix-web's features. This article will be a bit long as we'll be covering a handful of concepts. Let's get to it!

## Source code

The source code for this series is hosted on GitHub via:

{% github Sirneij/rust-auth %}

## Implementation

> You can get the overview of the code for this article on [github][1].

### Step 1: Install a couple of other dependencies

Since we'll be leveraging on rust's awesome ecosystem a lot, let's bring the crates we'll be using into our project:

```shell
~/rust-auth/backend$ cargo add pasetors once_cell hex chrono argon2 

~/rust-auth/backend$ cargo add uuid --features="v4,serde"

~/rust-auth/backend$ cargo add serde_json --features="raw_value"

~/rust-auth/backend$ cargo add minijinja --features="source"

~/rust-auth/backend$ cargo add lettre --features="builder,tokio1-native-tls"
```

That was quite some crates!!! Yeah but most of them are very common and popular. The table below summarizes what we'll be using them for:

| Crate         | Feature(s)     | Contextual usage     |
| :---          |    :----:            |          :--- |
| [pasetors][2] | all                 | To generate and verify secure tokens   |
| [once_cell][3]| all                | To load template source once and make it available when needed     |
| [hex][4] | all | To encode some randomly-generated 128 bytes of data which was used as `session_key` &mdash; a key used to temporarily store tokens in redis. |
| [chrono][5] | all | To effortlessly work with dates and time.|
| [argon2][6] | all | To hash and verify passwords as well as generate cryptographically secure random data.|
| [uuid][7] | v4, serde | To generate and work with version 4 and serde-compatible UUIDs. |
| [serde_json][8] | raw_value | To convert tokenized data into JSON object. |
| [minijinja][9] | source | To render HTML or multipart emails sent to users with their proper context values.|
| [lettre][10] | builder, tokio1-native-tls | To asynchronously and securely deliver emails to users. |

### Step 2: Write token and hashing logic

Now to some meaty stuff! We want to write the logic to securely generate and verify tokens as well as hash and verify passwords based on some best practices. We'll be needing a new module, `utils`, to help achieve this. Create the folder and make it a module as we have been doing. To be more organized, you should create a submodule, `auth`, where our token and password logic shall live. If you haven't wrapped your head around modules, check out the complete code for this article [here][1].

Let's start with passwords. Open `src/utils/auth/password.rs` in your editor and populate it with:

```rust
// src/utils/auth/password.rs

use argon2::{
    password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
    Argon2,
};

#[tracing::instrument(name = "Hashing user password", skip(password))]
pub async fn hash(password: &[u8]) -> String {
    let salt = SaltString::generate(&mut OsRng);
    Argon2::default()
        .hash_password(password, &salt)
        .expect("Unable to hash password.")
        .to_string()
}

#[tracing::instrument(name = "Verifying user password", skip(password, hash))]
pub async fn verify_password(
    hash: &str,
    password: &[u8],
) -> Result<(), argon2::password_hash::Error> {
    let parsed_hash = PasswordHash::new(hash)?;
    Argon2::default().verify_password(password, &parsed_hash)
}
```

Simple, huh?! Yeah. We are using the default setup of [argon2][6] to create a hash and verify it. Default setup? Isn't that too naive? No, I can tell you. The default setting uses the `argon2id` algorithm, `19456kB (19MiB)` memory cost, `2` number of iterations, and `1` degree of parallelism. Just as recommended by [OWASP's Password Storage Cheat Sheet][11].

Let's write the logic for token generation and verification next:

```rust
// src/utils/auth/tokens.rs

use argon2::password_hash::rand_core::{OsRng, RngCore};
use core::convert::TryFrom;
use deadpool_redis::redis::AsyncCommands;
use hex;
use pasetors::claims::{Claims, ClaimsValidationRules};
use pasetors::keys::SymmetricKey;
use pasetors::token::UntrustedToken;
use pasetors::{local, version4::V4, Local};

/// Store the session key prefix as a const so it can't be typo'd anywhere it's used.
const SESSION_KEY_PREFIX: &str = "valid_session_key_for_{}";

/// Issues a pasetor token to a user. The token has the user's id encoded.
/// A session_key is also encoded. This key is used to destroy the token
/// as soon as it's been verified. Depending on its usage, the token issued
/// has at most an hour to live. Which means, it is destroyed after its time-to-live.
#[tracing::instrument(name = "Issue pasetors token", skip(redis_connection))]
pub async fn issue_confirmation_token_pasetors(
    user_id: uuid::Uuid,
    redis_connection: &mut deadpool_redis::redis::aio::Connection,
    is_for_password_change: Option<bool>,
) -> Result<String, deadpool_redis::redis::RedisError> {
    // I just generate 128 bytes of random data for the session key
    // from something that is cryptographically secure (rand::CryptoRng)
    //
    // You don't necessarily need a random value, but you'll want something
    // that is sufficiently not able to be guessed (you don't want someone getting
    // an old token that is supposed to not be live, and being able to get a valid
    // token from that).
    let session_key: String = {
        let mut buff = [0_u8; 128];
        OsRng.fill_bytes(&mut buff);
        hex::encode(buff)
    };

    let redis_key = {
        if is_for_password_change.is_some() {
            format!(
                "{}{}is_for_password_change",
                SESSION_KEY_PREFIX, session_key
            )
        } else {
            format!("{}{}", SESSION_KEY_PREFIX, session_key)
        }
    };

    redis_connection
        .set(
            redis_key.clone(),
            // I just want to validate that the key exists to indicate the session is "live".
            String::new(),
        )
        .await
        .map_err(|e| {
            tracing::event!(target: "backend", tracing::Level::ERROR, "RedisError (set): {}", e);
            e
        })?;

    let settings = crate::settings::get_settings().expect("Cannot load settings.");
    let current_date_time = chrono::Local::now();
    let dt = {
        if is_for_password_change.is_some() {
            current_date_time + chrono::Duration::hours(1)
        } else {
            current_date_time + chrono::Duration::minutes(settings.secret.token_expiration)
        }
    };

    let time_to_live = {
        if is_for_password_change.is_some() {
            chrono::Duration::hours(1)
        } else {
            chrono::Duration::minutes(settings.secret.token_expiration)
        }
    };

    redis_connection
        .expire(
            redis_key.clone(),
            time_to_live.num_seconds().try_into().unwrap(),
        )
        .await
        .map_err(|e| {
            tracing::event!(target: "backend", tracing::Level::ERROR, "RedisError (expiry): {}", e);
            e
        })?;

    let mut claims = Claims::new().unwrap();
    // Set custom expiration, default is 1 hour
    claims.expiration(&dt.to_rfc3339()).unwrap();
    claims
        .add_additional("user_id", serde_json::json!(user_id))
        .unwrap();
    claims
        .add_additional("session_key", serde_json::json!(session_key))
        .unwrap();

    let sk = SymmetricKey::<V4>::from(settings.secret.secret_key.as_bytes()).unwrap();
    Ok(local::encrypt(
        &sk,
        &claims,
        None,
        Some(settings.secret.hmac_secret.as_bytes()),
    )
    .unwrap())
}

/// Verifies and destroys a token. A token is destroyed immediately
/// it has successfully been verified and all encoded data extracted.
/// Redis is used for such destruction.
#[tracing::instrument(name = "Verify pasetors token", skip(token, redis_connection))]
pub async fn verify_confirmation_token_pasetor(
    token: String,
    redis_connection: &mut deadpool_redis::redis::aio::Connection,
    is_password: Option<bool>,
) -> Result<crate::types::ConfirmationToken, String> {
    let settings = crate::settings::get_settings().expect("Cannot load settings.");
    let sk = SymmetricKey::<V4>::from(settings.secret.secret_key.as_bytes()).unwrap();

    let validation_rules = ClaimsValidationRules::new();
    let untrusted_token = UntrustedToken::<Local, V4>::try_from(&token)
        .map_err(|e| format!("TokenValiation: {}", e))?;
    let trusted_token = local::decrypt(
        &sk,
        &untrusted_token,
        &validation_rules,
        None,
        Some(settings.secret.hmac_secret.as_bytes()),
    )
    .map_err(|e| format!("Pasetor: {}", e))?;
    let claims = trusted_token.payload_claims().unwrap();

    let uid = serde_json::to_value(claims.get_claim("user_id").unwrap()).unwrap();

    match serde_json::from_value::<String>(uid) {
        Ok(uuid_string) => match uuid::Uuid::parse_str(&uuid_string) {
            Ok(user_uuid) => {
                let sss_key =
                    serde_json::to_value(claims.get_claim("session_key").unwrap()).unwrap();
                let session_key = match serde_json::from_value::<String>(sss_key) {
                    Ok(session_key) => session_key,
                    Err(e) => return Err(format!("{}", e)),
                };

                let redis_key = {
                    if is_password.is_some() {
                        format!(
                            "{}{}is_for_password_change",
                            SESSION_KEY_PREFIX, session_key
                        )
                    } else {
                        format!("{}{}", SESSION_KEY_PREFIX, session_key)
                    }
                };

                if redis_connection
                    .get::<_, Option<String>>(redis_key.clone())
                    .await
                    .map_err(|e| format!("{}", e))?
                    .is_none()
                {
                    return Err("Token has been used or expired.".to_string());
                }
                redis_connection
                    .del(redis_key.clone())
                    .await
                    .map_err(|e| format!("{}", e))?;
                Ok(crate::types::ConfirmationToken { user_id: user_uuid })
            }
            Err(e) => Err(format!("{}", e)),
        },

        Err(e) => Err(format!("{}", e)),
    }
}
```

That's a lot! Yeah, I know but trust me, glossing over them will simplify everything.

What `issue_confirmation_token_pasetors` does is to help generate secure paseto tokens with users' ID encoded for every registered user. To ensure that these tokens get destroyed after a user verifies his/her email address or when a user fails to verify his/her email address maybe for whatever reason, we also store the generated token in redis and utilize Redis's "Time To Live (TTL)" feature. Each token's "Time To Live" can be programmatically set in our `.yaml` files or via environment variables as will be discussed soon. We encode the key with which we store the token in redis as well so that we won't destroy another user's tokens erroneously. This entire scenario was what played out in the `verify_confirmation_token_pasetor` function. Pretty simple. It looked daunting because of the design decisions of the crate we used.

### Step 3: Create types and update settings

Your code will not compile for now due to a missing type, `ConfirmationToken`, and other setting variables. Let's update our settings:

```yaml
# settings/base.yaml
...
email:
  host: "smtp.gmail.com"
  host_user: ""
  host_user_password: ""

# settings/development.yaml
...
secret:
  secret_key: "YkDU_%q({@QV&5-Z}SONy,7YO?[qF6F6"
  token_expiration: 30
  hmac_secret: "3daad17f50d3577ae06406213073aa28e5cda75b97f5f35170e63653bbb66d8d"

frontend_url: "https://localhost:3000"

# settings/production.yaml
...
secret:
  secret_key: ""
  token_expiration: 0
  hmac_secret: ""

frontend_url: ""
```

We decided to make email credentials general. We'll be using Gmail as our SMTP server. I recommend you go through [this tutorial][12] to know how to generate your credentials if you haven't. The `secret` column stores some important strings heavily used for token generation. You should use different strings for dev and prod. You can generate them online and use environment variables to set them such as `APP_SECRET__SECRET_KEY=...` for setting `secret_key` and `APP_SECRET__HMAC_SECRET=...` for the `hmac_secret`. We also have `token_expiration` there which sets the number of minutes you want your generated tokens to expire.  `frontend_url` is the URL of your frontend application.

As you must have known by now, we need to effect these settings in `src/settings.rs`:

```rust
// src/settings.rs
...

/// Global settings for the exposing all preconfigured variables
#[derive(serde::Deserialize, Clone)]
pub struct Settings {
    ...
    pub secret: Secret,
    pub email: EmailSettings,
    pub frontend_url: String,
}

...
#[derive(serde::Deserialize, Clone)]
pub struct Secret {
    pub secret_key: String,
    pub token_expiration: i64,
    pub hmac_secret: String,
}

#[derive(serde::Deserialize, Clone)]
pub struct EmailSettings {
    pub host: String,
    pub host_user: String,
    pub host_user_password: String,
}
...
```

Next is the creation of the outstanding type, `ConfirmationToken`. As a good culture, we need good organization here too. So create a `types` module and in it create a `tokens.rs` file:

```rust
// src/types/tokens.rs

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct ConfirmationToken {
    pub user_id: uuid::Uuid,
}
```

### Step 4: Load templates and write email logic

Now, we want our HTML-based emails to be loaded by just providing their names or brief paths. To do this, let's utilize [once_cell][3] and [minijinja][9]. Open up `src/lib.rs` and append:

```rust
// src/lib.rs
...
pub static ENV: once_cell::sync::Lazy<minijinja::Environment<'static>> =
    once_cell::sync::Lazy::new(|| {
        let mut env = minijinja::Environment::new();
        env.set_source(minijinja::Source::from_path("templates"));
        env
    });
```

Those few lines of code lazily make available all files located in the `templates` folder at the root of our app. We will use it soon.

Let's now write the logic to really send emails. Again, we are using the `utils` module we created then:

```rust
// src/utils/emails.rs
use lettre::AsyncTransport;

#[tracing::instrument(
    name = "Generic e-mail sending function.",
    skip(
        recipient_email,
        recipient_first_name,
        recipient_last_name,
        subject,
        html_content,
        text_content
    ),
    fields(
        recipient_email = %recipient_email,
        recipient_first_name = %recipient_first_name,
        recipient_last_name = %recipient_last_name
    )
)]
pub async fn send_email(
    sender_email: Option<String>,
    recipient_email: String,
    recipient_first_name: String,
    recipient_last_name: String,
    subject: impl Into<String>,
    html_content: impl Into<String>,
    text_content: impl Into<String>,
) -> Result<(), String> {
    let settings = crate::settings::get_settings().expect("Failed to read settings.");

    let email = lettre::Message::builder()
        .from(
            format!(
                "{} <{}>",
                "JohnWrites",
                if sender_email.is_some() {
                    sender_email.unwrap()
                } else {
                    settings.email.host_user.clone()
                }
            )
            .parse()
            .unwrap(),
        )
        .to(format!(
            "{} <{}>",
            [recipient_first_name, recipient_last_name].join(" "),
            recipient_email
        )
        .parse()
        .unwrap())
        .subject(subject)
        .multipart(
            lettre::message::MultiPart::alternative()
                .singlepart(
                    lettre::message::SinglePart::builder()
                        .header(lettre::message::header::ContentType::TEXT_PLAIN)
                        .body(text_content.into()),
                )
                .singlepart(
                    lettre::message::SinglePart::builder()
                        .header(lettre::message::header::ContentType::TEXT_HTML)
                        .body(html_content.into()),
                ),
        )
        .unwrap();

    let creds = lettre::transport::smtp::authentication::Credentials::new(
        settings.email.host_user,
        settings.email.host_user_password,
    );

    // Open a remote connection to gmail
    let mailer: lettre::AsyncSmtpTransport<lettre::Tokio1Executor> =
        lettre::AsyncSmtpTransport::<lettre::Tokio1Executor>::relay(&settings.email.host)
            .unwrap()
            .credentials(creds)
            .build();

    // Send the email
    match mailer.send(email).await {
        Ok(_) => {
            tracing::event!(target: "backend", tracing::Level::INFO, "Email successfully sent!");
            Ok(())
        }
        Err(e) => {
            tracing::event!(target: "backend", tracing::Level::ERROR, "Could not send email: {:#?}", e);
            Err(format!("Could not send email: {:#?}", e))
        }
    }
}

#[tracing::instrument(
    name = "Generic multipart e-mail sending function.",
    skip(redis_connection),
    fields(
        recipient_user_id = %user_id,
        recipient_email = %recipient_email,
        recipient_first_name = %recipient_first_name,
        recipient_last_name = %recipient_last_name
    )
)]
pub async fn send_multipart_email(
    subject: String,
    user_id: uuid::Uuid,
    recipient_email: String,
    recipient_first_name: String,
    recipient_last_name: String,
    template_name: &str,
    redis_connection: &mut deadpool_redis::redis::aio::Connection,
) -> Result<(), String> {
    let settings = crate::settings::get_settings().expect("Unable to load settings.");
    let title = subject.clone();

    let issued_token = match crate::utils::issue_confirmation_token_pasetors(
        user_id,
        redis_connection,
        None,
    )
    .await
    {
        Ok(t) => t,
        Err(e) => {
            tracing::event!(target: "backend", tracing::Level::ERROR, "{}", e);
            return Err(format!("{}", e));
        }
    };
    let web_address = {
        if settings.debug {
            format!(
                "{}:{}",
                settings.application.base_url, settings.application.port,
            )
        } else {
            settings.application.base_url
        }
    };
    let confirmation_link = {
        if template_name == "password_reset_email.html" {
            format!(
                "{}/users/password/confirm/change_password?token={}",
                web_address, issued_token,
            )
        } else {
            format!(
                "{}/users/register/confirm/?token={}",
                web_address, issued_token,
            )
        }
    };
    let current_date_time = chrono::Local::now();
    let dt = current_date_time + chrono::Duration::minutes(settings.secret.token_expiration);

    let template = crate::ENV.get_template(template_name).unwrap();
    let ctx = minijinja::context! {
        title => &title,
        confirmation_link => &confirmation_link,
        domain => &settings.frontend_url,
        expiration_time => &settings.secret.token_expiration,
        exact_time => &dt.format("%A %B %d, %Y at %r").to_string()
    };
    let html_text = template.render(ctx).unwrap();

    let text = format!(
        r#"
        Tap the link below to confirm your email address.
        {}
        "#,
        confirmation_link
    );
    tokio::spawn(send_email(
        None,
        recipient_email,
        recipient_first_name,
        recipient_last_name,
        subject,
        html_text,
        text,
    ));
    Ok(())
}
```

Though some lines, skimming through the lines will simplify them. In the `send_email` function, we were just carefully formulating the multipart emails, with text-only fallback, and providing the necessary credentials to ensure a successful and secure email delivery. That function was a direct modification of [this][13] and [this][14] examples available in the crate's repo. `send_multipart_email` issues a token to the user, builds the confirmation link sent to such a user, renders a template with the context values sent into it, and then surrenders the sending of the email to tokio. It's like putting the email in a queue and sending it on a different thread. 

For the email verification, we have this HTML:

```HTML
<!--templates/verification_email.html-->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{{ title }}</title>
  </head>

  <body>
    <table
      style="
        max-width: 555px;
        width: 100%;
        font-family: 'Open Sans', Segoe, 'Segoe UI', 'DejaVu Sans',
          'Trebuchet MS', Verdana, sans-serif;
        background: #fff;
        font-size: 13px;
        color: #323232;
      "
      cellspacing="0"
      cellpadding="0"
      border="0"
      bgcolor="#ffffff"
      align="center"
    >
      <tbody>
        <tr>
          <td align="left">
            <h1 style="text-align: center">
              <span style="font-size: 15px">
                <strong>{{ title }}</strong>
              </span>
            </h1>

            <p>Tap the button below to verify your email address.</p>

            <table
              style="
                max-width: 555px;
                width: 100%;
                font-family: 'Open Sans', arial, sans-serif;
                font-size: 13px;
                color: #323232;
              "
              cellspacing="0"
              cellpadding="0"
              border="0"
              bgcolor="#ffffff"
              align="center"
            >
              <tbody>
                <tr>
                  <td height="10">&nbsp;</td>
                </tr>
                <tr>
                  <td style="text-align: center">
                    <a
                      href="{{ confirmation_link }}"
                      style="
                        color: #fff;
                        background-color: hsla(199, 69%, 84%, 1);
                        width: 320px;
                        font-size: 16px;
                        border-radius: 3px;
                        line-height: 44px;
                        height: 44px;
                        font-family: 'Open Sans', Arial, helvetica, sans-serif;
                        text-align: center;
                        text-decoration: none;
                        display: inline-block;
                      "
                      target="_blank"
                      data-saferedirecturl="https://www.google.com/url?q={{ confirmation_link }}"
                    >
                      <span style="color: #000000">
                        <strong>Verify email address</strong>
                      </span>
                    </a>
                  </td>
                </tr>
              </tbody>
            </table>

            <table
              style="
                max-width: 555px;
                width: 100%;
                font-family: 'Open Sans', arial, sans-serif;
                font-size: 13px;
                color: #323232;
              "
              cellspacing="0"
              cellpadding="0"
              border="0"
              bgcolor="#ffffff"
              align="center"
            >
              <tbody>
                <tr>
                  <td height="10">&nbsp;</td>
                </tr>
                <tr>
                  <td align="left">
                    <p align="center">&nbsp;</p>
                    If the above button doesn't work, try copying and pasting
                    the link below into your browser. If you continue to
                    experience problems, please contact us.
                    <br />
                    {{ confirmation_link }}
                    <br />
                  </td>
                </tr>
                <tr>
                  <td>
                    <p align="center">&nbsp;</p>
                    <br />
                    <p style="padding-bottom: 15px; margin: 0">
                      Kindly note that this link will expire in
                      <strong>{{expiration_time}} minutes</strong>. The exact
                      expiration date and time is:
                      <strong>{{ exact_time }}</strong>.
                    </p>
                  </td>
                </tr>
              </tbody>
            </table>
          </td>
        </tr>
      </tbody>
    </table>
  </body>
</html>
```

If you have ever used templating engines, you will not be lost.

### Step 5: Write registration route logic

Let's finally write our registration logic:

```rust
// src/routes/users/register.rs
use sqlx::Row;

#[derive(serde::Deserialize, Debug, serde::Serialize)]
pub struct NewUser {
    email: String,
    password: String,
    first_name: String,
    last_name: String,
}

#[derive(serde::Deserialize, serde::Serialize)]
pub struct CreateNewUser {
    email: String,
    password: String,
    first_name: String,
    last_name: String,
}

#[tracing::instrument(name = "Adding a new user",
skip( pool, new_user, redis_pool),
fields(
    new_user_email = %new_user.email,
    new_user_first_name = %new_user.first_name,
    new_user_last_name = %new_user.last_name
))]
#[actix_web::post("/register/")]
pub async fn register_user(
    pool: actix_web::web::Data<sqlx::postgres::PgPool>,
    new_user: actix_web::web::Json<NewUser>,
    redis_pool: actix_web::web::Data<deadpool_redis::Pool>,
) -> actix_web::HttpResponse {
    let mut transaction = match pool.begin().await {
        Ok(transaction) => transaction,
        Err(e) => {
            tracing::event!(target: "backend", tracing::Level::ERROR, "Unable to begin DB transaction: {:#?}", e);
            return actix_web::HttpResponse::InternalServerError().json(
                crate::types::ErrorResponse {
                    error: "Something unexpected happend. Kindly try again.".to_string(),
                },
            );
        }
    };
    let hashed_password = crate::utils::hash(new_user.0.password.as_bytes()).await;

    let create_new_user = CreateNewUser {
        password: hashed_password,
        email: new_user.0.email,
        first_name: new_user.0.first_name,
        last_name: new_user.0.last_name,
    };

    let user_id = match insert_created_user_into_db(&mut transaction, &create_new_user).await {
        Ok(id) => id,
        Err(e) => {
            tracing::event!(target: "sqlx",tracing::Level::ERROR, "Failed to insert user into DB: {:#?}", e);
            let error_message = if e
                .as_database_error()
                .unwrap()
                .code()
                .unwrap()
                .parse::<i32>()
                .unwrap()
                == 23505
            {
                crate::types::ErrorResponse {
                    error: "A user with that email address already exists".to_string(),
                }
            } else {
                crate::types::ErrorResponse {
                    error: "Error inserting user into the database".to_string(),
                }
            };
            return actix_web::HttpResponse::InternalServerError().json(error_message);
        }
    };

    // send confirmation email to the new user.
    let mut redis_con = redis_pool
        .get()
        .await
        .map_err(|e| {
            tracing::event!(target: "backend", tracing::Level::ERROR, "{}", e);
            actix_web::HttpResponse::InternalServerError().json(crate::types::ErrorResponse {
                error: "We cannot activate your account at the moment".to_string(),
            })
        })
        .expect("Redis connection cannot be gotten.");

    crate::utils::send_multipart_email(
        "RustAuth - Let's get you verified".to_string(),
        user_id,
        create_new_user.email,
        create_new_user.first_name,
        create_new_user.last_name,
        "verification_email.html",
        &mut redis_con,
    )
    .await
    .unwrap();

    if transaction.commit().await.is_err() {
        return actix_web::HttpResponse::InternalServerError().finish();
    }

    tracing::event!(target: "backend", tracing::Level::INFO, "User created successfully.");
    actix_web::HttpResponse::Ok().json(crate::types::SuccessResponse {
        message: "Your account was created successfully. Check your email address to activate your account as we just sent you an activation link. Ensure you activate your account before the link expires".to_string(),
    })
}

#[tracing::instrument(name = "Inserting new user into DB.", skip(transaction, new_user),fields(
    new_user_email = %new_user.email,
    new_user_first_name = %new_user.first_name,
    new_user_last_name = %new_user.last_name
))]
async fn insert_created_user_into_db(
    transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
    new_user: &CreateNewUser,
) -> Result<uuid::Uuid, sqlx::Error> {
    let user_id = match sqlx::query(
        "INSERT INTO users (email, password, first_name, last_name) VALUES ($1, $2, $3, $4) RETURNING id",
    )
    .bind(&new_user.email)
    .bind(&new_user.password)
    .bind(&new_user.first_name)
    .bind(&new_user.last_name)
    .map(|row: sqlx::postgres::PgRow| -> uuid::Uuid{
        row.get("id")
   })
    .fetch_one(&mut *transaction)
    .await
    {
        Ok(id) => id,
        Err(e) => {
            tracing::event!(target: "sqlx",tracing::Level::ERROR, "Failed to insert user into DB: {:#?}", e);
            return Err(e);
        }
    };

    match sqlx::query(
        "INSERT INTO user_profile (user_id) 
                VALUES ($1) 
            ON CONFLICT (user_id) 
            DO NOTHING
            RETURNING user_id",
    )
    .bind(user_id)
    .map(|row: sqlx::postgres::PgRow| -> uuid::Uuid { row.get("user_id") })
    .fetch_one(&mut *transaction)
    .await
    {
        Ok(id) => {
            tracing::event!(target: "sqlx",tracing::Level::INFO, "User profile created successfully {}.", id);
            Ok(id)
        }
        Err(e) => {
            tracing::event!(target: "sqlx",tracing::Level::ERROR, "Failed to insert user's profile into DB: {:#?}", e);
            Err(e)
        }
    }
}
```

I am sorry if that overwhelms you. Again, looking through will calm your nerves. We expect a user to provide `email`, `first_name`, `last_name`, and `password` as JSON. We used actix-web's [JSON extractors][15] to get this data from the user. Since we'll be doing two operations at a go, that is creating the user and his/her profile, we want to ensure that if one fails, the previous ones should be reversed or rollback. Hence the use of [transaction][16]. After that, we hashed the password provided by the user and called the `insert_created_user_into_db` to do what its name implies. In the `insert_created_user_into_db`, we used raw SQL to insert the user into the DB. If successful, the user's ID is returned and fed into the next query which tends to create the user's profile. If all is good, we return the user's ID. This ID is one of the things sent over to `send_multipart_email`, the function previously discussed. We then did:

```rust
...
if transaction.commit().await.is_err() {
        return actix_web::HttpResponse::InternalServerError().finish();
}
```

To persist our data in the DB. If this is not done, our user data will not be persisted. Then we return a success message. In case an error was encountered at any point, an appropriate error message will be sent to the user. Thanks to Rust's awesome error-handling prowess. We will do this a lot in the coming articles.

### Step 6: Connect the registration route to the application

The last bit of the work is making our application aware of our newly created route. To do this, I chose a rather organized way of doing things. We'll be using actix-web's [service configuration][17] to split our routes into modules for modularity. Open up `src/routes/users/mod.rs`:

```rust
// src/routes/users/mod.rs
...
pub fn auth_routes_config(cfg: &mut actix_web::web::ServiceConfig) {
    cfg.service(actix_web::web::scope("/users").service(register::register_user));
}
```

We scoped our users' module to have an endpoint that looks like `/users/...`. For registration, it'll be `/users/register/`. Next is `src/routes/mod.rs`:

```rust
// src/routes/mod.rs
...
pub use users::auth_routes_config;
```

And then, `src/startup.rs`:

```rust
// src/startup.rs
...
    .service(crate::routes::health_check)
    // Authentication routes
    .configure(crate::routes::auth_routes_config)
...
```

With this, we are good!!!

That was nothing short of a long ride... I personally think it was worth it. See you soon.

## Outro

Enjoyed this article? Consider [contacting me for a job, something worthwhile or buying a coffee ☕](mailto:sirneij@gmail.com). You can also connect with/follow me on [LinkedIn](https://www.linkedin.com/in/idogun-john-nelson/) and [Twitter](https://twitter.com/Sirneij). It isn't bad if you help share this article for wider coverage. I will appreciate it...

[1]: https://github.com/Sirneij/rust-auth/pull/4/commits/bde61e0d3b00adc4aec0d8f864746682485483d6 "User registeration and security added"
[2]: https://crates.io/crates/pasetors "PASETO: Platform-Agnostic Security Tokens (in Rust)"
[3]: https://crates.io/crates/once_cell "Single assignment cells and lazy values."
[4]: https://crates.io/crates/hex "Encoding and decoding data into/from hexadecimal representation."
[5]: https://crates.io/crates/chrono "Date and time library for Rust"
[6]: https://crates.io/crates/argon2 "Pure Rust implementation of the Argon2 password hashing function with support for the Argon2d, Argon2i, and Argon2id algorithmic variants"
[7]: https://crates.io/crates/uuid "A library to generate and parse UUIDs."
[8]: https://crates.io/crates/serde_json "A JSON serialization file format"
[9]: https://crates.io/crates/minijinja "A powerful template engine for Rust with minimal dependencies"
[10]: https://crates.io/crates/lettre "A mailer library for Rust"
[11]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#password-storage-cheat-sheet "Password Storage Cheat Sheet"
[12]: https://dev.to/abderrahmanemustapha/how-to-send-email-with-django-and-gmail-in-production-the-right-way-24ab "Send emails with Django and Gmail , a better way"
[13]: https://github.com/lettre/lettre/blob/master/examples/tokio1_smtp_starttls.rs "Asynchronous implementations"
[14]: https://github.com/lettre/lettre/blob/master/examples/basic_html.rs "Create an HTML email."
[15]: https://actix.rs/docs/extractors#json "JSON extractors"
[16]: https://docs.rs/sqlx/latest/sqlx/struct.Transaction.html "SQLx transaction"
[17]: https://actix.rs/docs/application#configure "Configure"