DEV Community

Alain
Alain

Posted on

Reason Tutorial Mashup using Context Part 2

In Part 1 we did the simple version which can be found here:

Alt Text

Let kick it up a notch by trying it using aws-amplify authentication in this same app.

Plenty of stuff material around on setting up AWS. https://dev.to/dabit3 is good place to start.

Once you have the aws-cli configured, run amplify init in the root of the project from part 1.

It should look something like this:

Alt Text

Then run amplify add auth to get a Cognito Identity Pool and Cognito User Pool set up.

Alt Text

Be sure to run amplify push to get all the backend set up in the cloud.

Since we didn't set up signing in we want to create a test user in our UserPool via the aws cognito interface on aws. That didn't sound clear, let me know if you don't get what I mean. In your terminal run amplify console auth which will open up that page.

Alt Text

Select User Pool then enter. This will open up the AWS Cognito Users page in your User Pool. On the menu on the left, click Users and Groups then the blue outlined Create User button.

Alt Text

This is how I filled it out.

Alt Text

The password I used was Password12345@ so cognito wouldn't complain.

Even though it says that we will need to update the password, we are dealing with that here and it will let you use the temporary password for a while. Cognito will also send it to you in an email because we check that option.

Setting Up Aws Auth In The App

Bindings!

The first thing we want to do is add the aws-amplify package. We will use it to configure aws-amplify and run auth functions.

yarn add aws-amplify
touch Amplify.re // create a file for our Amplify binding.
Enter fullscreen mode Exit fullscreen mode

Then create a file for our Amplify binding.

touch Amplify.re
Enter fullscreen mode Exit fullscreen mode

In Amplify.re we want to add the following:

type t;
[@bs.module "aws-amplify"] external amplify: t = "default";
type config;
[@bs.module "./aws-exports.js"] external awsConfig: config = "default";

[@bs.send] external _configure: (t, config) => unit = "configure";
let configure = () => _configure(amplify, awsConfig);
Enter fullscreen mode Exit fullscreen mode

What is going on here?

Ripped from Patrick Kilgore's BigInteger.re

What is type t?

It is a ocaml convention for "the type of this module". So if the module was named "Fish", type t would be the fish. We could just as easily call it anything else, even type fish, but then we'd be referring to it as Fish.fish which seems silly, right? So we call it type t and refer to it as Fish.t and by convention know t means the module's type.

A ReasonML module is a type packaged with its behavior. In this way, it is similar to an Object-Oriented language's concept of class.

So here, type t is the Amplify data structure, packaged with the methods we can to operate on that type.

Because we don't really know (or, honestly, care) about how the Amplify library implements the Amplify type, we just declare it here, which means it is an "abstract type", which I always think of as, "a type that must be used consistently by the functions that operate on it, but for which the particular implementation of the type and those functions are assumed to be correct".

Thanks, Patrick for taking the time to write those awesome comments.

So t is our Amplify javascript data structure bound to aws-amplify's default export.

The type config may or may not be overkill. I would love to hear back from you all on this. It works without it but its a pattern I picked up somewhere and this code works so moving on. We are using bs.module to import the aws-exports.js file that the amplify-cli generated in our src dir when we ran amplify push. It's got our configuration keys for accessing our auth service.

We are going to pass to that to Amplify's configure method/function which configures our app to use our services. We use [@bs.send] to call the function called configure on out type t. I aliased it as _configure so that I could call it using configure, no underscore later, and not hurt my eyes trying to see which configure function I was calling. In Reason, you can call them both configure and the second configure will just call the previous configure.

Normally in JS it would look like this in your app's entry point:

import Amplify, { Auth } from 'aws-amplify';
import awsconfig from './aws-exports';
Amplify.configure(awsconfig);
Enter fullscreen mode Exit fullscreen mode

I went ahead and retrieve aws-exports and passed it to configure here. So in our app's entry point we can configure our app like so:

...other stuff
Amplify.configure(); //add this line
ReactDOMRe.renderToElementWithId(<Root />, "root");
Enter fullscreen mode Exit fullscreen mode

Also in Amplify.re we want to add a binding to Amplify's Auth object. Let's add the following bindings and implementations functions:

/* assigning Amplify Auth object as type auth */
type auth;

[@bs.module "aws-amplify"] external auth: auth = "Auth";

[@bs.send] external _signOut: (auth, unit) => unit = "configure";


[@bs.send]
external _signIn:
  (auth, ~username: string, ~password: string, unit) => Js.Promise.t('a) =
  "signIn";

/* a function that calls Amplify's signOut to sign out our user. This works wether passing auth or amplify as our type t */

let signOut = () => _signOut(auth, ());

/* a function that takes a username and password then calls Amplify's signIn to sign in our user */

let signIn = (~username, ~password) =>
  _signIn(auth, ~username, ~password, ())
  |> Js.Promise.then_(res => Js.Promise.resolve(res));

Enter fullscreen mode Exit fullscreen mode

By binding to the Auth object and assigning type auth we can use this same binding to call its functions using [bs.send]. We tell the compiler that the function is found on the auth binding by passing requiring an argument with type auth in our bs.send definitions like so:

[@bs.send]
external _signIn:
  (auth, ~username: string, ~password: string, unit) => Js.Promise.t('a) =
  "signIn";

Enter fullscreen mode Exit fullscreen mode

The implementation is written so that when we call signIn it only requires the username and password which we then pass to the the underscore signIn which already has the auth binding called in it.


let signIn = (~username, ~password) =>
  _signIn(auth, ~username, ~password, ())
  |> Js.Promise.then_(res => Js.Promise.resolve(res));

Enter fullscreen mode Exit fullscreen mode

I am pretty sure, this is what they call currying. The docs aren't very helpful so let me take a stab at explaining it to us. The _signin already has the auth property and is just waiting on the last two variables that it needs to be able to make the call. These remaining variables are the username and password values we pass into signIn(). This makes it so we don't have to pass in the auth property at the call sites every time we want to use the module. Anyone with a better explanation, please teach me!

Using Our Binding

Now that we have the binding, let use them in the Header.re module.

We are going to add to functions that will handle signIn and signOut.

// ...other code
let handleSignin = () =>
    Js.Promise.(
      Amplify.signIn(~username, ~password)
      |> then_(res => {
           //  Js.log2("res", res);
           // this is bad, i think, because we aren't handling errors. We know, for purposes of the example, that the username is at the `username` key so let's go with it.
           let username = res##username;

           Js.log("sign in success!");
           dispatch(UserLoggedIn(username));
           resolve();
         })
      |> catch(err => {
           Js.log(err);
           let errMsg = "error signing in.." ++ Js.String.make(err);
           Js.log(errMsg);
           resolve();
         })
      |> ignore
    );
  let handleSignOut = () => {
    Amplify.signOut();
    dispatch(UserLoggedOut);
    Js.log("signing out!");
    /* test if user is logged out because you can still log the user after logging out. Running currentAuthenticated user shows that we are logged out so why is `user` logging out below?*/
    Amplify.currentAuthenticatedUser
    |> Js.Promise.then_(data => {
         Js.log2("data", data);
         Js.Promise.resolve(data);
       })
    |> Js.Promise.catch(error => Js.log2("error", error)->Js.Promise.resolve)
    |> Js.Promise.resolve
    |> ignore;
    /* user still logs after logging out. Why? */
    Js.log2("signing out user!",user);
  };

// ...other code

Enter fullscreen mode Exit fullscreen mode

The handleSignIn function is going to read the username and password off of our state and call Amplify.signIn with it. If we get a positive answer, then we read the username key off of the response object,res##username and set it in our user context by calling dispatch(UserLoggedIn(username)). The ## is how you read the value at a key on a javascript object. See Accessors in the bucklescript docs.

The handleSignOut is pretty simple since it doesn't return anything. I added a call to currentAuthenticatedUser because you can still log the username after signing out. In fact, the currentAuthenticatedUser response shows that we are signed out. If anyone wants to tell me why the username is still logging, I would love to understand it. I though it would error or return Anonymous. Idea? Ideas? Thank's in advance.

Now let change:


| Anonymous =>
    <form
      className="user-form"
      onSubmit={e => {
        ReactEvent.Form.preventDefault(e);
        dispatch(UserLoggedIn(userName));
      }}>

Enter fullscreen mode Exit fullscreen mode

To:


  | Anonymous =>
    <form
      className="user-form"
      onSubmit={e => {
        ReactEvent.Form.preventDefault(e);
        handleSignin();
      }}>
Enter fullscreen mode Exit fullscreen mode

And further down, change:

| LoggedIn(userName) =>
    <div className="user-form">
      <span className="logged-in">
        {s("Logged in as: ")}
        <b> {s(userName)} </b>
      </span>
      <div className="control">
        <button
          className="button is-link"
          onClick={_ => dispatch(UserLoggedOut)}>
          {s("Log Out")}
        </button>
        </div>
    </div>

Enter fullscreen mode Exit fullscreen mode

to:

| LoggedIn(userName) =>
    <div className="user-form">
      <span className="logged-in">
        {s("Logged in as: ")}
        <b> {s(userName)} </b>
      </span>
      <div className="control">
       <button className="button is-link" onClick={_ => handleSignOut()}>
      </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

That's it. Now you are using Aws Cognito to for overkill authentication in Ms. Brandt's music app.

Reach with questions or lessons, please. Thank you!

Check out this version on the with-aws branch

Top comments (0)