OAuth Requests, APIs, Diesel, and Sessions
Tags: rust, beginners, webapp, oauth
Published: 2019-08-09 14:47:16

---
title: OAuth Requests, APIs, Diesel, and Sessions
published: true
tags: rust,beginners,WebApp,oauth
canonical_url: https://rust.graystorm.com/2019/08/09/oauth-requests-apis-diesel-and-sessions/
cover_image: https://rust.graystorm.com/wp-content/uploads/2019/06/plant-wood-old-rust-equipment-gear-1278587-pxhere.com_.jpg
series: Designing a Full-Featured WebApp with Rust
---

![Intertwined woven basket material.. much like Oauth, accounts, APIs, diesel, and sessions are in this project (probably any project)](https://i1.wp.com/rust.graystorm.com/wp-content/uploads/2019/08/wheel-spiral-purple-line-pasture-metal-969319-pxhere.com_.jpg?resize=400%2C400&ssl=1)<figcaption>Things are starting to intertwine… </figcaption>

Some big changes in the [repository](https://github.com/jculverhouse/pinpoint_shooting) just recently. I added `Google Signin` and `Facebook Signin` [OAuth](https://en.wikipedia.org/wiki/OAuth) connections. I’m thinking I may not even configure an internal password on the site for users and instead just require one of those options. Probably I’ll add more, like [500px.com](https://500px.com/) and/or [Flickr](https://www.flickr.com/), given the site’s purpose. A `password` field is still in my database though, so I haven’t given up the idea completely. Also, the OAuth requests create accounts using Diesel.

`Users` (now identified as `shooters` – we photographers haven’t given up that term) – are now written to the db. I really fought with one Diesel feature, so that bit is still commented out in the code. In addition, I added my first API to `POST` to – so another step with the `Rocket`crate as well! I’d like to work my way into playing with a [GraphQL](https://graphql.org/) endpoint so I can play with that as well!! (What’s the limit on Crate dependencies in a project anyway?!) I’m starting to think I won’t be able to tackle all of this in a single post – but let start!

## OAuth vs Old and New Accounts

When a user arrives on the site, I check for a `cookie` with a `session id` (see my [previous post](https://rust.graystorm.com/2019/08/04/rust-web-app-session-management-with-aws/)). I decided, for now, I would use the `User Agent` (plus some random characters) as a fingerprint for creating the session id. So, when I am able to get a `session_id` from a cookie, I want to verify the `User Agent` is the same and that the session hasn’t expired. If the user arrives brand new, without a cookie, I immediately create an empty, no-user session for them. All of this is done, for now, right at the top of my `index() route`.

```
<src/routes.rs>

...
#[get("/")]
pub fn index(mut cookies: Cookies, nginx: Nginx) -> 
    rocket_contrib::templates::Template
{
    let session = get_or_setup_session(&mut cookies, &nginx);
...
```

After the `index` page loads, it shows the Google and Facebook Sign In buttons. Clicking one of those, they do the validation dance and get permission from the user. When that is granted, my app gets a token back which I send up to the server via a `POST` to `/api/v1/tokensignin`.

```
<src/api.rs>

use rocket::{http::Cookies, post, request::Form, FromForm};
use crate::oauth::*;
use crate::routes::Nginx;
use crate::session::*;
#[derive(FromForm)]
pub struct OAuthReq {
    pub g_token: Option<String>,  // google login req
    pub fb_token: Option<String>, // facebook login req`
    pub name: String,
    pub email: String,
}
#[post("/api/v1/tokensignin", data = "<oauth_req>")]
pub fn tokensignin(mut cookies: Cookies, nginx: Nginx,
        oauth_req: Form<OAuthReq>) -> String
{
    let mut session = get_or_setup_session(&mut cookies, &nginx);
    if let Some(token) = &oauth_req.g_token {
        match verify_google_oauth(&mut session, &token,
            &oauth_req.name, &oauth_req.email)
        {
            true => {
                session.google_oauth = true;
                save_session_to_ddb(&mut session);
                "success".to_string()
            }
            false => {
                session.google_oauth = false;
                save_session_to_ddb(&mut session);
                "failed".to_string()
            }
        }
    } else if let Some(token) = &oauth_req.fb_token {
        match verify_facebook_oauth(&mut session, &token,
            &oauth_req.name, &oauth_req.email)
        {
            true => {
                session.facebook_oauth = true;
                save_session_to_ddb(&mut session);
                "success".to_string()
            }
            false => {
                session.facebook_oauth = false;
                save_session_to_ddb(&mut session);
                "failed".to_string()
            }
        }
    } else {
        "no token sent".to_string()
    }
}

```

## OAuth Requests via HTTP POSTs

This is how you allow for a `POST` and `form data` to come in – you setup a `struct` (`OAuthReq` in my example) of what you expect and bring that in as an input param. Plus, I am also bringing in any `cookies` that arrive with the request plus some Nginx headers so I have access to `UserAgent`. In the code so far, I’m either verifying a Google or Facebook token. Let’s look at the Google example (the Facebook one is nearly the same). Here are the relevant parts, but I’ll break some pieces down and go through it:

```
<src/oauth.rs>

...
pub fn verify_google_oauth(
    session: &mut Session,
    token: &String,
    name: &String,
    email: &String,
) -> bool {
    let mut google = google_signin::Client::new();
    google.audiences.push(CONFIG.google_api_client_id.clone());
    let id_info = google.verify(&token).expect("Expected token to be valid");
    let token = id_info.sub.clone();
    verify_token(session, "google".to_string(), &token, &name, &email)
}
```

Which leads right away to a big `match`:

```
fn verify_token(
    session: &mut Session,
    vendor: String,
    token: &String,
    name: &String,
    email: &String,
) -> bool {
    use crate::schema::oauth::dsl::*;
    use crate::schema::shooter::dsl::*;
    let connection = connect_pgsql();
    match oauth
        .filter(oauth_vendor.eq(&vendor))
        .filter(oauth_user.eq(&token))
        .first::<Oauth>(&connection)
    {
```

With the OK arm:

```
        // token WAS found in oauth table
        Ok(o) => {
            if let Some(id) = session.shooter_id {
                if id == o.shooter_id {
                    return true;
                } else {
                    return false;
                }
            } else {
                // log in user - what IS the problem with BelongsTo!?
                //if let Ok(s) = Shooter::belonging_to(&o)
                //    .load::<Shooter>(&connection)
                //{
                //    session.shooter_id = Some(shooter.shooter_id);
                //    session.shooter_name = Some(shooter.shooter_name);
                //    session.email_address = Some(shooter.email);
                return true;
                //} else {
                //    return false;
                //}
            }
        }
```

And the ERR arms:

```
        // token not found in oauth table
        Err(diesel::NotFound) => match session.shooter_id {
            Some(id) => {
                create_oauth(&connection, &vendor, token, id);
                true
            }
            None => match shooter
                .filter(shooter_email.eq(&email))
                .first::<Shooter>(&connection)
            {
                // email address WAS found in shooter table
                Ok(s) => {
                    create_oauth(&connection, &vendor, token, s.shooter_id);
                    true
                }
                // email address not found in shooter table
                Err(diesel::NotFound) => {
                    let this_shooter =
                        create_shooter(&connection, name, None,
                            email, &"active".to_string());
                    session.shooter_id = Some(this_shooter.shooter_id);
                    create_oauth(&connection, &vendor, token,
                        this_shooter.shooter_id);
                    true
                }
                Err(e) => {
                    panic!("Database error {}", e);
                }
            },
        },
        Err(e) => {
            panic!("Database error {}", e);
        }
    }
}
```

## Simple Queries with Diesel

Breaking all that code down to smaller bits: first, I query the PgSQL database for the given `oauth user`:

```
match oauth
    .filter(oauth_vendor.eq(&vendor))
    .filter(oauth_user.eq(&token))
    .first::<Oauth>(&connection) {
        Ok(o) => { ... }
        Err(diesel::NotFound) => { ... }
        Err(e) => { ... }
}
```

Check the `oauth` table for records WHERE (`filter`) the `oauth_vendor` is (google or facebook) AND I’ve already stored the same validated `oauth_user`. I will get back either `Ok(o)` or `Err(diesel::NotFound)` … (or some worse error message), so I make a pattern with those 3 arms.

If we did get a hit from the DB, that `session id` is already tied to a `shooter_id` (user id) unless something is very wrong. So, IF we also have a `shooter_id` defined in our current `session`, I just need to verify that they match and return true or false. But, if we don’t have a `shooter_id` in our `session`, we know the `oauth` is tied to a `shooter` in the db, so this will log them in. `Diesel` has an easy way to get that parent record, which is what this should do:

```
// if let Ok(s) = Shooter::belonging\_to(&o).load::\<Shooter\>(&connection) {
...
```

I fought and fought to get this work, but you can see it is still commented out. From posts and chat around the Internet, I believe it can work – I think I either have a `scope` problem or my `models` aren’t setup correctly… this is how they look:

```
<src/model.rs>

...
#[derive(Identifiable, Queryable, Debug, PartialEq)]
#[table_name = "shooter"]
#[primary_key("shooter_id")]
pub struct Shooter {
    pub shooter_id: i32,
    pub shooter_name: String,
    pub shooter_password: String,
    pub shooter_status: String,
    pub shooter_email: String,
    pub shooter_real_name: String,
    pub shooter_create_time: chrono::NaiveDateTime,
    pub shooter_active_time: Option<chrono::NaiveDateTime>,
    pub shooter_inactive_time: Option<chrono::NaiveDateTime>,
    pub shooter_remove_time: Option<chrono::NaiveDateTime>,
    pub shooter_modify_time: chrono::NaiveDateTime,
}
...
#[derive(Identifiable, Associations, Queryable, Debug, PartialEq)]
#[belongs_to(Shooter, foreign_key = "shooter_id")]
#[table_name = "oauth"]
#[primary_key("oauth_id")]
pub struct Oauth {
    pub oauth_id: i32,
    pub oauth_vendor: String,
    pub oauth_user: String,
    pub shooter_id: i32,
    pub oauth_status: String,
    pub oauth_create_time: chrono::NaiveDateTime,
    pub oauth_last_use_time: chrono::NaiveDateTime,
    pub oauth_modify_time: chrono::NaiveDateTime,
}
```

I’ll get it to work eventually – I really hope it isn’t failing because I didn’t specifically name my primary fields just `id` like in the examples `Diesel` gives in their guides. It seems like naming `shooter_id` in table `oauth` to match `shooter_id` in the `shooter` table should make things obvious. Hopefully we aren’t forced to always use `id` as the primary field… no, that can’t be it.

Anyway, back to verifying. The other main case is that an `oauth` record with this token is NOT found in the table. Which means it is a new connection we haven’t seen before. If the `session` is already logged in, we just need to attach this `oauth` token to the logged in user and return true!

```
Some(id) => {
    create_oauth(&connection, &vendor, token, id);
    true
}
```

Otherwise, two choices – we will try to match on an existing `shooter` via the `email address`. If we find a match, we log them in and again attach this oauth token to their `shooter` record.

```
 None => match shooter
     .filter(shooter_email.eq(&email))
     .first::<Shooter>(&connection)
 {
     // email address WAS found in shooter table
     Ok(s) => {
         create_oauth(&connection, &vendor, token, s.shooter_id);
         true
     }
```

Otherwise, we don’t get a hit; that is, we haven’t seen this `oauth` token before AND we haven’t seen this validated `email address` before. We have to call that a brand new `shooter` account. I mentioned we create accounts from the OAuth requests using Diesel – this is where that happens. In this case, we create both the `shooter` record and the `oauth` record, linking them together.

```
// email address not found in shooter table
Err(diesel::NotFound) => {
    let this_shooter =
        create_shooter(&connection, name, None, email,
            &"active".to_string());
    session.shooter_id = Some(this_shooter.shooter_id);
    create_oauth(&connection, &vendor, token, this_shooter.shooter_id);
    true
}
```

## Using Diesel to Insert Records

As we fall back out of the stack of functions we’ve called, because we return `true` here the `session` will get updated with the `shooter_id` – they are now logged in. Also, the `shooter` and `oauth` records are saved, so if they come back, they can just validate and be logged into their same account again. Here are the two methods that create those records:

```
<src/shooter.rs>

...
pub fn create_shooter<'a>(
    connection: &PgConnection,
    name: &'a String,
    password: Option<&'a String>,
    email: &'a String,
    status: &'a String,
) -> Shooter {
    use crate::schema::shooter::dsl::*;
    let new_shooter = NewShooter {
        shooter_name: name.to_string(),
        shooter_password: match password {
            Some(p) => p.to_string(),
            None => thread_rng()
                .sample_iter(&Alphanumeric)
                .take(64)
                .collect::<String>(),
        },
        shooter_status: status.to_string(),
        shooter_email: email.to_string(),
        shooter_real_name: name.to_string(),
    };
    diesel::insert_into(shooter)
        .values(&new_shooter)
        .get_result(connection)
        .expect("Error saving new Shooter")
}
```

```
<src/oauth.rs>

...
pub fn create_oauth<'a>(
    connection: &PgConnection,
    vendor: &'a String,
    user_id: &'a String,
    shooterid: i32,
) -> Oauth {
    use crate::schema::oauth::dsl::*;
    let new_oauth = NewOauth {
        oauth_vendor: vendor.to_string(),
        oauth_user: user_id.to_string(),
        shooter_id: shooterid,
    };
    diesel::insert_into(oauth)
        .values(&new_oauth)
        .get_result(connection)
        .expect("Error saving new Oauth")
}
```

As far as writing these new records to PgSQL – in both cases, we have `NewShooter` and `NewOauth structs` that allow us to set the bare minimum of fields without having to worry about the fields that PgSQL will default for us (like the `create_date` fields). We setup the appropriate `struct` and pass it to `insert_into()`. Adding `.get_result()` will return the newly created record to us, so we have access to the brand new `shooter_id` or `oauth_id`.

## Complexity

If a user comes to the site, signs in with one OAuth (which creates their shooter record and attaches that oauth token) and then signs in with the other, this logic figures out they are validated to be the same person, so creates just a single shooter record with two oauth records, and both point to the one user. If they come back, they can authenticate via either third-party and are allowed back in.

Ok, more to come as I figure out other problems. I haven’t gone through that logic tightly enough to make sure I don’t have any holes – and it wouldn’t surprise me to find some. It doesn’t really matter – this is certainly teaching me Rust! Give it a try at [PinpointShooting.com](https://pinpointshooting.com/) – but don’t be surprised if you `shooter` account gets deleted, constantly.

The post [OAuth Requests, APIs, Diesel, and Sessions](https://rust.graystorm.com/2019/08/09/oauth-requests-apis-diesel-and-sessions/) appeared first on [Learning Rust](https://rust.graystorm.com).