The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter
Tags: javascript, webdev, github, security
Published: 2020-09-08 17:12:23

---
title: The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter
published: true
date: 2020-09-08 06:55:07 UTC
tags: javascript, webdev, github, security
canonical_url: https://blog.cotter.app/the-simplest-way-to-authorize-github-oauth-apps-with-nextjs-and-cotter/
---
*Add "Sign in with Github" in one click to authorize your Github OAuth App and access Github REST API using Cotter and Next.js.*

 ![The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter](https://blog.cotter.app/content/images/2020/09/Cover-1.png)

Cotter just launched a [Github Login integration](https://docs.cotter.app/sdk-reference/web/sign-in-with-social-login-provider) 🎉. This means you can easily log your users in and get an access token to enable Github integrations in your app.

## What we're building

We're going to build a website with [Next.js](https://nextjs.org/) that allows your users to login with email or with Github and **get a list of their public and private repositories**.

![Sign in with Github using Cotter and Next.js to use Github API](https://dev-to-uploads.s3.amazonaws.com/i/pyjqxli38259z94al5vp.gif)<figcaption>Sign in with Github using Cotter and Next.js to use Github API</figcaption>

### Overview
- [Let's Start - Make our Home Page](#lets-start-make-our-home-page)
- [Let's see how this works before moving on to Github API](#lets-see-how-this-works-before-moving-on-to-github-api)
- [Designing our API endpoints to get data from Github](#designing-our-api-endpoints-to-get-data-from-github)
- [Showing the Repo List in our Dashboard Page](#showing-the-repo-list-in-our-dashboard-page)
- [But, what if the user didn't Sign in with Github?](#but-what-if-the-user-didnt-sign-in-with-github)

# Let's Start – Make our Home Page

## Create your Next.js project

Start with creating a new Next.js project by running the code below, and follow the instructions.

```bash
yarn create next-app
```

## Add a Login Form in the Home Page

We're using Cotter for the Login form to quickly enable an [Email Magic Link](https://docs.cotter.app/quickstart-guides/react-magic-link-with-email-and-phone) login and [Sign in with Github](https://docs.cotter.app/sdk-reference/web/sign-in-with-social-login-provider).

### Add Cotter as a dependency

```bash
yarn add cotter
```

### Add a Login Form and a Title

Modify our home page at `pages/index.js`. We'll start with the simple Email Magic Link login. Remove everything in `pages/index.js` and add a title and Cotter's login form:

```javascript
import { useEffect } from "react";
import styles from "../styles/Home.module.css";
import Cotter from "cotter"; // 1️⃣ Import Cotter
import { useRouter } from "next/router";

export default function Home() {
  const router = useRouter();
  // 2️⃣ Initialize and show the form
  useEffect(() => {
    var cotter = new Cotter(API_KEY_ID); // 👈 Specify your API KEY ID here
    cotter
      .signInWithLink() // use .signInWithOTP() to send an OTP
      .showEmailForm() // use .showPhoneForm() to send magic link to a phone number
      .then((response) => {
        console.log(response); // show the response
        router.push("/dashboard");
      })
      .catch((err) => console.log(err));
  }, []);

  return (
      <div className={styles.container}>
        <h1 className={styles.subtitle}>Welcome to my Github App</h1>

        {/* 3️⃣ Put a <div> that will contain the form */}
        <div id="cotter-form-container" style={{ width: 300, height: 300 }} />
      </div>
  );
}

```
<figcaption>pages/index.js</figcaption>

You'll need an `API_KEY_ID`, create a new project, and [copy the `API_KEY_ID` from the dashboard](https://dev.cotter.app/). The code above should give you a simple Login page that looks like this:

![The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter](https://blog.cotter.app/content/images/2020/09/image-2.png)

### Enable Github Login

[The documentation laid out the steps you need to take to enable Social Login](https://docs.cotter.app/sdk-reference/web/sign-in-with-social-login-provider#setting-up-social-login-provider) to your login form. We'll follow it step by step below:

**First, create a Github OAuth App.** Summarizing [Github's documentation](https://docs.github.com/en/developers/apps/creating-an-oauth-app), you should do the following:

- Click on your profile picture on the top right > Settings > Developer Settings > OAuth Apps > New OAuth App
- Fill in your Application Name, Homepage URL, and description based on your app.
- Fill in  [`https://www.cotter.app/api/v0/oauth/token/callback/GITHUB`](https://www.cotter.app/api/v0/oauth/token/callback/GITHUB) for the **Authorization callback URL.**
- Then click Register Application.

![The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter](https://blog.cotter.app/content/images/2020/09/image-3.png)

**Go to your Cotter Dashboard and add a Social Login connection.**

Go to [Dashboard](https://dev.cotter.app/) > Social Login > New Login Connection > Github. Then copy your Client ID and Client Secret from Github. We'll add the `repo` scope because we want to get the users' repository data.

![The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter](https://blog.cotter.app/content/images/2020/09/image-11.png)<figcaption>Add the Client ID, Client Secret, and repo scope</figcaption>

Press **Create** to create the login connection.

**Show Github Login on your Form**

Now that your Social Login connection is set up, we can show it in our Login Form. Go to [Dashboard](https://dev.cotter.app/) > Branding > Magic Link. Check the box for **Github** under Social Login Providers.

![The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter](https://blog.cotter.app/content/images/2020/09/image-12.png)<figcaption>Add Github Login to your Form</figcaption>

Press **Save** to update your customization.

### You should now see the Sign in with Github button in our Next.js app.

![The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter](https://blog.cotter.app/content/images/2020/09/image-13.png)<figcaption>Sign in with Github enabled on Cotter Login Form</figcaption>

## Let's see how this works before moving on to Github API

We'll go over how the login works, how you can authenticate users to your backend, and how you can get Github's access token to access private repo data.

### 1. Let's try logging in with your email address first. 

Enter your email address and press "Sign in Without Password". Tap the magic link in your email and you should be logged-in.

**Now check your console log,** you should see something like this:

```json
{
  "token": {...},
  "email": "team@cotter.app", // 👈 the user's email
  "oauth_token": {
    "access_token": "eyJhbGciOiJFUzI...", // 👈 access token
    "id_token": "eyJhbGciOiJFUzI1...",
    "refresh_token": "236:QDVxW6...",
    "expires_in": 3600,
    "token_type": "Bearer",
    "auth_method": "OTP"
  },
  "user": {
    "ID": "abcdefgh-abcd-abcd-9959-67ebae3cdfcf", // 👈 user ID
    "issuer": "abcdefgh-abcd-abcd-81ad-5cc8b69051e8",
    "identifier": "team@cotter.app",
    ...
  }
}
```

> Note: If your console log can't be expanded because you redirected to `/dashboard`, comment out the redirection part `router.push("/dashboard")`

Three things that you should take note of are **the user's email** , **the Cotter User ID,** and the **access\_token** that we will use to protect our API endpoints. These information will be available to you at anytime the user is logged-in by calling `cotter.tokenHandler.getAccessToken()` and `cotter.getLoggedInUser()`

### 2. Let's try logging-in again but with your Github account that has the same email address

When you used a Github account that has the same address as an existing account, you should see a prompt asking if you'd like to link the accounts:

![The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter](https://blog.cotter.app/content/images/2020/09/image-18.png)

If you're using a Github account that has an email address that is not recognized, then it will automatically create a new user. **You'll see the same JSON response like the above when the user has successfully log in with Github.**

# Designing our API endpoints to get data from Github

1. We will have a dashboard page that will call our API endpoint at `/api/repo` to get a list of repositories owned by the user.
2. We'll make an API endpoint `/api/repo` that will:

- Check if the user is logged-in
- If logged-in, get the user's Github Access Token from [Cotter's API](https://docs.cotter.app/sdk-reference/web/sign-in-with-social-login-provider/getting-access-tokens-from-social-login-providers#get-an-access-token-from-a-specific-provider-for-a-user)
- Call Github API to get [the authenticated user's repository list](https://blog.cotter.app/p/1b117450-1da7-4c18-af10-b470aa68ccce/docs.cotter.app)

## Make our API endpoint at `/api/repo`

Our endpoint will look like this:

```http
GET http://localhost:3000/api/repo
Authorization: Bearer <Cotter Access Token>
```

### 1. Make a function to handle API calls to `/api/repo`

Next.js gives you a neat way to add server code that can handle API requests. To handle an API call to `/api/repo`, **make a file `pages/api/repo.js`.** Then, we'll add a skeleton handler function with a list of things that we need to do:

```javascript
const handler = async (req, res) => {
  // TODO: Check if Authorization Header has a valid access_token
  // TODO: Parse the access_token to get cotter_user_id to
  // TODO: Call Cotter's API to get Github Access Token for the user
  // TODO: Call Github API to get the repository data
};

export default handler;

```
<figcaption>pages/api/repo.js</figcaption>
### 2. Check if the Authorization Header have a valid access token

We'll make a separate function above our `handler` function to do this check. We'll be using Cotter's client library to help us validate the access token.

```bash
yarn add cotter-node
```

```javascript
// 1) Import Cotter
import { CotterValidateJWT } from "cotter-node";

const checkJWT = (handler) => async (req, res) => {
  // 2) Check that the access_token exists
  if (!("authorization" in req.headers)) {
    res.statusCode = 401;
    res.end("Authorization header missing");
    return;
  }
  const auth = await req.headers.authorization;
  const bearer = auth?.split(" ");
  const token = bearer?.length > 0 && bearer[1];

  // 3) Validate the access_token
  var valid = false;
  try {
    valid = await CotterValidateJWT(token);
  } catch (e) {
    console.log(e);
    valid = false;
  }
  if (!valid) {
    res.statusCode = 403;
    res.end("Authorization header is invalid");
    return;
  }

  // 4) Pass the access token to the next handler
  req.access_token = token;
  handler(req, res);
};

const handler = async (req, res) => {...};
                                     
// 5) We are passing our handler function into
// `checkJWT` so that `checkJWT` will be run first
// before our `handler` is run.
export default checkJWT(handler); 

```
<figcaption>pages/api/repo.js</figcaption>

What we did was pretty simple:

- First, we check if the Authorization header exists
- If it exists, then we check if the `access_token` is valid using Cotter's helper function.
- Then we call `checkJWT(handler)` to run the check and then run the handler if the check passed.

### 3. Get the Cotter User ID from the `access_token`.

We will need this for our API call to Cotter. The `access_token` is a JWT token that contains the user's Cotter User ID. [Check here for the full spec](https://docs.cotter.app/getting-access-token/handling-authentication-with-cotter#cotters-access-token). We'll use another Cotter helper function to parse the access token and get the Cotter User ID.

```bash
yarn add cotter-token-js
```

```javascript
import { CotterValidateJWT } from "cotter-node";
// 1) Import Cotter Token
import { CotterAccessToken } from "cotter-token-js";

const checkJWT = (handler) => async (req, res) => {...};

const handler = async (req, res) => {
  // Parse the access_token to get cotter_user_id
  const decodedToken = new CotterAccessToken(req.access_token);
  const cotterUserID = decodedToken.getID();
  // TODO: Call Cotter's API to get Github Access Token for the user
  // TODO: Call Github API to get the repository data
};

export default checkJWT(handler);

```
<figcaption>pages/api/repo.js</figcaption>

### 4. Get Github Access Token from Cotter API

The [API to get a Social Provider access token from Cotter](https://docs.cotter.app/sdk-reference/web/sign-in-with-social-login-provider/getting-access-tokens-from-social-login-providers#get-an-access-token-from-a-specific-provider-for-a-user) looks like this

```bash
curl -XGET \
-H 'API_KEY_ID: <COTTER API KEY ID>' \
-H 'API_SECRET_KEY: <COTTER API SECRET KEY>' \
'https://www.cotter.app/api/v0/oauth/token/GITHUB/<COTTER USER ID>'
```

**Let's install axios and create our request**

```bash
yarn add axios
```

```javascript
import axios from "axios"; // Import axios

const checkJWT = (handler) => async (req, res) => {...};

const handler = async (req, res) => {
  // Parse the access_token to get cotter_user_id
  ...

  // Call Cotter's API to get Github Access Token for the user
  let githubAccessToken = "";
  const config = {
    headers: {
      API_KEY_ID: process.env.COTTER_API_KEY_ID,
      API_SECRET_KEY: process.env.COTTER_API_SECRET_KEY,
    },
  };
  try {
    let resp = await axios.get(
     `https://www.cotter.app/api/v0/oauth/token/GITHUB/${cotterUserID}`,
      config
    );
    githubAccessToken = resp.data.tokens?.access_token;
  } catch (err) {
    res.statusCode = 500;
    res.end("Fail getting Github access token from Cotter API");
    return;
  }
  // TODO: Call Github API to get the repository data
};

export default checkJWT(handler);
```
<figcaption>pages/api/repo.js</figcaption>

As you can see we're storing our secrets in an environment variable. Get your `API_KEY_ID` and `API_SECRET_KEY` from the [dashboard](https://dev.cotter.app/) and export it in your terminal, then run `yarn dev`.

```bash
$ export COTTER_API_KEY_ID=<API KEY ID>
$ export COTTER_API_SECRET_KEY=<API SECRET KEY>
$ yarn dev
```

### 5. Call Github API to get the repositories list

Github's API to get the list of repositories of the authenticated user looks like this:

```bash
curl \
  -H "Accept: application/vnd.github.v3+json" \
  -H "Authorization: token <GITHUB ACCESS TOKEN>" \
  "https://api.github.com/user/repos"
```

Let's make the request using axios and the Github Access Token that we get in the earlier step.

```javascript
const handler = async (req, res) => {
  // Parse the access_token to get cotter_user_id to
  ...
  // Call Cotter's API to get Github Access Token for the user
  ...

  // Call Github API to get the repository data
  const githubConfig = {
    headers: {
      Accept: "application/vnd.github.v3+json",
      Authorization: `token ${githubAccessToken}`,
    },
  };
  try {
    let resp = await axios.get(
      `https://api.github.com/user/repos`,
      githubConfig
    );
    // We only want to show the repo name and url
    const repoData = resp.data?.map((repo) => ({
      full_name: repo.full_name,
      url: repo.html_url,
    }));
    res.statusCode = 200;
    res.json(repoData);
    return;
  } catch (err) {
    res.statusCode = 500;
    res.end("Fail getting repostories from Github API");
    return;
  }
};

export default checkJWT(handler);
```
<figcaption>pages/api/repo.js</figcaption>

### That's it, let's try our API endpoint

Copy your access token from the console log when logging in and run:

```bash
curl \
  -H "Authorization: Bearer <COTTER ACCESS TOKEN>" \
  "http://localhost:3000/api/repo"
```

You should see the following response:

```json
[
  {
    "full_name": "putrikarunia/project1",
    "url": "https://github.com/putrikarunia/project1"
  },
  {
    "full_name": "putrikarunia/project2",
    "url": "https://github.com/putrikarunia/project2"
  },
  {
    "full_name": "putrikarunia/project3",
    "url": "https://github.com/putrikarunia/project3"
  }
]
```
<figcaption>Response from our backend at <code>/api/repo</code></figcaption>
# Showing the Repo List in our Dashboard Page

## Make the Dashboard Page

Add a dashboard page by making a file at `pages/dashboard.js`. Using `useEffect` we will call our API endpoint to get the repositories, and put the results in our React state:

```javascript
import { useEffect, useState } from "react";
import styles from "../styles/Home.module.css";
import Cotter from "cotter";
import axios from "axios";

export default function Dashboard() {
  const [err, seterr] = useState(null);
  const [repos, setrepos] = useState([]);

  // Get a list of repositories
  useEffect(() => {
    getRepositories();
  }, []);

  const getRepositories = async () => {
    // 1️⃣ Get Access Token for Logged-in User
    var cotter = new Cotter(API_KEY_ID); // 👈 Specify your API KEY ID here
    const accessToken = await cotter.tokenHander.getAccessToken();

    // 2️⃣ Make the request to our `/api/repo` endpoint
    const config = {
      headers: {
        Authorization: `Bearer ${accessToken?.token}`,
      },
    };
    try {
      let resp = await axios.get("/api/repo", config);
      setrepos(resp.data);
    } catch (err) {
      seterr(JSON.stringify(err.response?.data));
    }
  };

  return (
    <div className={styles.container}>
      <h1 className={styles.subtitle}>
        Welcome! Here's a list of your Github Repos
      </h1>
      {/* Show any error here */}
      <div style={{ color: "#FF0000" }}>{err}</div>

      {/* 3️⃣ Show the list of repositories */}
      <div className={styles.main}>
        {repos.map((repo) => (
          <div className={styles.card}>
            <h3>{repo.full_name}</h3>
            <a href={repo.url}>{repo.url}</a>
          </div>
        ))}
      </div>
    </div>
  );
}

```
<figcaption> pages/dashboard.js</figcaption>

Let's go over what we did:

- We added 2 React states, `err` and `repos`, to show errors and the repo data.
- When the component mounts, we call `getRepositories` which first gets the user's access token using Cotter's function `cotter.tokenHandler.getAccessToken()`, then calls an API request to our backend endpoint at `/api/repo`.
- When the API call is successful, the function will update our `repos` state with the list of repositories, or show an error.

### If you sign-in with Github, then go to `localhost:3000/dashboard` , you'll see the following:

> Don't forget to uncomment the redirection part in `/pages/index.js` : `router.push("/dashboard")`

![The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter](https://blog.cotter.app/content/images/2020/09/image-19.png)<figcaption>A list of your Github Repositories</figcaption>

## Add a NavBar to Log Out or Log In and navigate between pages

Let's add a Navbar component to help our users navigate our website. Make a file `/components/Navbar/index.js` in your project directory.

```javascript
import { useState, useEffect } from "react";
import Link from "next/link";
import Cotter from "cotter";

export default function Navbar() {
  const [loggedIn, setloggedIn] = useState(false);
  const [email, setemail] = useState(null);
  useEffect(() => {
    checkLoggedIn();
  }, []);

  // TODO: Check if the user is logged-in
  const checkLoggedIn = async () => {};

  // TODO: Log out the user
  const logOut = () => {};

  return (
    <div style={{ display: "flex", justifyContent: "flex-end" }}>
      {loggedIn ? (
        <div style={{ padding: 20 }} onClick={logOut}>
          Log Out
        </div>
      ) : (
        <Link href="/">
          <a style={{ padding: 20 }}>Log In</a>
        </Link>
      )}

      {loggedIn && <div style={{ padding: 20 }}>{email}</div>}
      <Link href="/dashboard">
        <a style={{ padding: 20 }}>Go to Dashboard</a>
      </Link>
    </div>
  );
}

```
<figcaption>components/Navbar/index.js</figcaption>

- We added a `loggedIn` and `email` state. If the user is logged-in, we'll display the Log Out button and the user's email, otherwise we'll display the Login button.
- The function `checkLoggedIn` will check if the user is logged in and update the `loggedIn` state and set the user's `email` state
- We also added a function called `logOut` to log out the user.

### Make the `checkLoggedIn` function

We can do this using Cotter's function by checking if an access token exists. Update your `checkLoggedIn` function:

```javascript
  const checkLoggedIn = async () => {
    const cotter = new Cotter(API_KEY_ID); // 👈 Specify your API KEY ID here
    const accessToken = await cotter.tokenHander.getAccessToken();
    if (accessToken?.token.length > 0) {
      setloggedIn(true);
      const user = cotter.getLoggedInUser();
      setemail(user?.identifier);
    } else {
      setloggedIn(false);
    }
  };
```
<figcaption>components/Navbar/index.js</figcaption>

### Make the `logOut` function

We can also do this by calling Cotter's `cotter.logOut()` function. Update your `logOut` function:

```javascript
  const logOut = async () => {
    const cotter = new Cotter(API_KEY_ID); // 👈 Specify your API KEY ID here
    await cotter.logOut();
    setloggedIn(false);
    window.location.href = "/";
  };
```
<figcaption>components/Navbar/index.js</figcaption>
### Import the Navbar in your Home Page and Dashboard Page

**In `/pages/index.js`:**

```javascript
import Navbar from "../components/Navbar";

export default function Home() {
  ...
  return (
    <>
      <Navbar /> // Add the navbar
      <div className={styles.container}>...</div>
    </>
  );
}

```
<figcaption>pages/index.js</figcaption>

**In `/pages/dashboard.js`:**

```javascript
import Navbar from "../components/Navbar";

export default function Dashboard() {
  ...
  return (
    <>
      <Navbar /> // Add the navbar
      <div className={styles.container}>...</div>
    </>
  );
}

```
<figcaption>pages/dashboard.js</figcaption>

### Great! Now our website works well and users can log in/log out and get their Repositories list.

![The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter](https://blog.cotter.app/content/images/2020/09/image-20.png)


# But, what if the user didn't Sign in with Github?

If the user didn't Sign in with Github, then we wouldn't get Github's Access Token, and it will return an error like this:

![The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter](https://blog.cotter.app/content/images/2020/09/image-21.png)

### How do we fix this?

Fortunately, Cotter has a function to allow **logged-in users** to **Connect a Github Account of their choosing to their current account.** This means we can add a button in the dashboard that tells the user to Connect Github if we get this error.

## Add a button to Connect Github if not connected yet.

Following the guide to [Connect a Github account to an Existing User](https://docs.cotter.app/sdk-reference/web/sign-in-with-social-login-provider#connecting-a-github-account-to-an-existing-user), we'll add a function and a button at `pages/dashboard.js`

```javascript
import Cotter from "cotter";
export default function Dashboard() {
  ...

  // Get a list of repositories
  useEffect(() => {...}, []);

  const getRepositories = async () => {...};

  const connectToGithub = async () => {
    var cotter = new Cotter(API_KEY_ID); // 👈 Specify your API KEY ID here
    const accessToken = await cotter.tokenHandler.getAccessToken();
    cotter.connectSocialLogin("GITHUB", accessToken?.token); // pass in the provider's name
  };

  return (
    <>
      <Navbar />
      <div className={styles.container}>
        {/* Show any error here */}
        ...

        {/* If there's no Github access token, show a button to connect a Github account */}
        {err?.includes("Fail getting Github access token from Cotter API") && (
          <div className={styles.card} onClick={connectToGithub}>
            Connect Github
          </div>
        )}

        {/* Show the list of repositories */}
        ...
      </div>
    </>
  );
}

```
<figcaption>pages/dashboard.js</figcaption>

**Now let's try logging in with an email that is not associated with your Github account using the `Email Address` field.** You should see something like this:

![The Simplest Way to Authorize Github OAuth Apps with Next.js and Cotter](https://blog.cotter.app/content/images/2020/09/image-22.png)<figcaption>Show a Connect Github button if the user doesn't have any Github Account connected</figcaption>

**Press Connect Github** , and it will **connect your currently logged-in Github Account with this email address.**

If you log out and log in again with Github, you will now be logged-in to this new email address.

### How do I disconnect a Github Account

We won't cover this in the tutorial, but you can use our [API endpoint to delete a connection.](https://docs.cotter.app/sdk-reference/web/sign-in-with-social-login-provider/getting-access-tokens-from-social-login-providers#delete-an-access-token-from-a-specific-provider-for-a-user)

# That's it!

We now have a working Github API integration with a simple way to get your user's Github Access Token.

![Sign in with Github using Cotter and Next.js to use Github API](https://dev-to-uploads.s3.amazonaws.com/i/pyjqxli38259z94al5vp.gif)<figcaption>Sign in with Github using Cotter and Next.js to use Github API</figcaption>

### What's Next?

There's a lot of things that you can do using Github's API.

- Check out the complete list of [Github's REST API here](https://docs.github.com/en/rest). 
- See the code for this tutorial in our [Github repo](https://github.com/cotter-code/guide-github-oauth).

* * *

### Questions & Feedback

Come and talk to the founders of Cotter and other developers who are using Cotter on [Cotter's Slack Channel](https://join.slack.com/t/askcotter/shared_invite/zt-dxzf311g-5Mp3~odZNB2DwYaxIJ1dJA).

### Ready to use Cotter?

If you enjoyed this tutorial and want to integrate Cotter into your website or app, you can [create a free account](https://dev.cotter.app/) and [check out our documentation](https://docs.cotter.app/).

If you need help, ping us on our [Slack channel](https://join.slack.com/t/askcotter/shared_invite/zt-dxzf311g-5Mp3~odZNB2DwYaxIJ1dJA) or email us at team@cotter.app.