Files
medium-aws-tutorial-1/tutorial.md
2020-03-18 03:25:33 +00:00

7.5 KiB

AWS amplify

Example repo

Use the following repo as an example/baseline:

https://git.panaetius.co.uk/web-development/medium-aws-tutorial-1

Installaing amplify

Install amplify globally

yarn global add @aws-amplify/cli

Create new amplify app in project

amplify init

Amplify commands

Amplify commands after init

amplify status - will show you what you've added already and if it's locally configured or deployed amplify add <category> - will allow you to add features like user login or a backend API amplify push - will build all your local backend resources and provision it in the cloud amplify console - to open the Amplify Console and view your project status amplify publish - will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

amplify add api - to create a backend API and then amplify publish - to deploy everything

Install aws amplify dependencies into project yarn add aws-amplify aws-amplify-vue

Create auth flow

Add the Amplify instance to Vue instance in main.js

Auth.js

Create a new js file in ./src/utils/auth.js - write the aws amplify code in here.

You need to write a function for each auth flow. Signup, GetUser etc.

Remember to use export {getUser, signUp, confirmSignUp, resendSignUp, signIn, signOut} at the end so it can be imported later.

You should write a view for each action, login, signup etc. These views should define the UI, and also call the function in the auth.js. E.g if you need to sign in, you should write the UI, write any computed methods to verify the form, and write a submit method that takes the form details and calls the signIn function from the auth.js file.

In ./utils/auth.js you can write the functions needed to actually log in with amplify.

For example, to signUp you can do:

function signUp(username, password) {
  return Auth.signUp({
    username,
    password,
    attributes: {
      email: username
    }
  })
    .then(data => {
      AmplifyEventBus.$emit("localUser", data.user);
      if (data.userConfirmed === false) {
        AmplifyEventBus.$emit("authState", "confirmSignUp");
      } else {
        AmplifyEventBus.$emit("authState", "signIn");
      }
    })
    .catch(err => {
      console.log(err);
    });
}

router/index.js

In the ./router/index.js you should write the router flow.

Define routes

Each route should set whether they need authentication or not.

  {
    path: "/",
    name: "Home",
    component: Home,
    meta: { requiresAuth: true }
  },

Additional functionality

In this file, you can write functions that control the behaviour. For example, you can force anyone logged in to go to the home page only:

getUser().then(user => {
  if (user) {
    router.push({ path: "/" });
  }
});

Respond to events

AWS Amplify has the ability to natively emit events in your auth flow functions. When you emit these, you can use AmplifyEventBus to do something.

We can push people to different routes based on the sign in flow.

An example emit:

AmplifyEventBus.$emit("authState", "signedIn");

An example listener in the router:

AmplifyEventBus.$on("authState", async state => {
  const pushPathes = {
    signedOut: () => {
      router.push({ path: "/signIn" });
    },
    signUp: () => {
      router.push({ path: "/signUp" });
    },
    confirmSignUp: () => {
      router.push({ path: "/signUpConfirm" });
    },
    signIn: () => {
      router.push({ path: "/signIn" });
    },
    signedIn: () => {
      router.push({ path: "/" });
    }
  };
  if (typeof pushPathes[state] === "function") {
    pushPathes[state]();
  }
});

Managing sign in/out + displaying name

Create the Vuex store

state

Your state should contain 3 items:

  • authorized:bool = flag if logged in or not.
  • user: str = user object.
  • userEmail: str = username or email.

mutations

You should define two mutations (to update this state):

  mutations: {
    user(state, user) {
      state.authorized =
        !!user && user.attributes && user.attributes.email_verified;
      state.user = user;
    },
    userEmail(state, userEmail) {
      state.userEmail = userEmail;
    }
  },
  • When you set the user state you should check to make sure the user object you pass in exists and set the authorized flag at the same time.

actions

You should define an action that fetches the user. This should call the getUser method of whatever auth system you're using and pass this in to the method.

This getUser dispatch should either get the user and set the store state, or set it to null if no user exists. This way, any interaction with getUser will make sure the store is up to date.

  actions: {
    async fetchUser({ commit }) {
      try {
        const user = await Auth.currentAuthenticatedUser();
        const expires =
          user.getSignInUserSession().getIdToken().payload.exp -
          Math.floor(new Date().getTime() / 1000);
        console.log(`Token expires in ${expires} seconds.`);
        commit("user", user);
        commit("userEmail", user.attributes.email);
      } catch (err) {
        commit("user", null);
        commit("userEmail", null);
      }
    }
  },

getters

You should define getters to access the state. These getters will cache - so they will only update if one of the things used to calculate the state updates.

  getters: {
    userEmail: state => {
      return state.userEmail;
    },
    authorized: state => {
      return state.authorized;
    }
  }

Triggering state for apps

In addition to getting the user to determine router actions, you should get the user in the main component so you can set the state.

In the main view (parent of all components App.vue) you should define a created method. This method should:

  • Fetch the user.
  • Change the state to reflect this.
  async created() {
    try {
      await this.$store.dispatch("fetchUser");
    } catch (error) {
      console.log(error);
    } finally {
      console.log(this.email);
      console.log(this.$store.state.userEmail);
    }
  },

This needs to be done right at the start to make sure this triggers a state change before anything else.

In the views for signing in/out/registering etc you should make sure that when you call the auth methods you wrote, that you also update the state at this time. This way, any component in the app can use the state to determine if someone is logged in, and display/toggle components dynamically.

A signin method for a button might look like:

    async submit() {
      if (this.$refs.form.validate()) {
        console.log(
          `SIGN IN username: ${this.username}, password: ${this.password}`
        );
        try {
          await signIn(this.username, this.password);
        } catch (err) {
          console.log(err);
        } finally {
          this.$store.dispatch("fetchUser");
        }
        console.log("Signed in");
      }
    }

A sign out button might look like:

    async dosignOut() {
      try {
        await signOut();
      } finally {
        this.$store.dispatch("fetchUser");
      }
    }

Remember:

  • Call this.$store.dispatch("fetchUser) when you sign in - do it in a try/finally block to make sure the state gets updated after the ajax call.
  • Call this.$store.dispatch("fetchUser) when you sign out do it in a try/finally bock to make sure the state gets updated after the ajax call.