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

283 lines
7.5 KiB
Markdown

# 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:
```javascript
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.
```javascript
{
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:
```javascript
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:
```javascript
AmplifyEventBus.$emit("authState", "signedIn");
```
An example listener in the router:
```javascript
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):
```javascript
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.
```javascript
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.
```javascript
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.
```javascript
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:
```javascript
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:
```javascript
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.