Securing a Go-Backed Scrappy Twitter API with Magic
Tags: go, passwordless, authentication, magic
Published: 2021-01-20 23:35:34

---
title: Securing a Go-Backed Scrappy Twitter API with Magic
published: true
description: Learn how to protect API routes for a Go-backed application using the Magic Admin SDK for Go.
tags: go, passwordless, authentication, magic
cover_image: https://i.imgur.com/CFwjS3R.png
---

Hi there 🙋🏻‍♀️. The Scrappy Twitter API is a Go-backend project that is secured by the [Magic Admin SDK for Go](https://docs.magic.link/admin-sdk/go/get-started). This SDK makes it super easy to leverage Decentralized ID (DID) Tokens to authenticate your users for your app. 

# Table of Contents
  * [Demo](#demo)
  * [A High-Level View](#high-level)
  * [Getting Started](#getting-started)
    * [Prerequisites](#prerequisites)
    * [Magic](#magic)
    * [Server](#server)
    * [Client](#client)
    * [Postman](#postman)
  * [The Scrappy Twitter Go Rest API](#go-api)
    * [File Structure](#file-structure)
    * [A Local Database](#local-db)
    * [The Routes and Handlers](#routes-handlers)
  * [Securing the Go Rest API with Magic Admin SDK](#secure-go-api)
    * [Magic Setup](#magic-setup)
    * [Magic Admin SDK for Go](#magic-sdk-go)
      * [checkBearerToken()](#check-bearer-token)
      * [handleRequests()](#handle-requests)
      * [createATweet()](#create-tweet)
      * [deleteATweet()](#delete-tweet)

# Demo <a name="demo"></a>
To test out our Live demo, click the button below to import the Magic-secured Scrappy Twitter API Postman collection.

[![Run in Postman](https://run.pstmn.io/button.svg)](https://god.postman.co/run-collection/595abf685418eeb96401)

Alternatively, you could also manually import this static snapshot of the collection: 

`https://www.getpostman.com/collections/595abf685418eeb96401`

![Postman Collection] (https://i.imgur.com/VYC0h7G.gif, "Magic Auth x Webflow")

This Postman collection has the following routes:

- **POST a tweet** (protected): https://scrappy-secure-go-twitter-api.wl.r.appspot.com/tweet
- **GET all tweets** (unprotected): https://scrappy-secure-go-twitter-api.wl.r.appspot.com/tweets
- **GET a single tweet** (unprotected): https://scrappy-secure-go-twitter-api.wl.r.appspot.com/tweet/1
- **DELETE a tweet** (protected): https://scrappy-secure-go-twitter-api.wl.r.appspot.com/tweet/2

You’ll only be able to make requests with the `Get All Tweets` and `Get a Single Tweet` endpoints because they’re unprotected. To post or delete a tweet, you’ll need to pass in a DID token to the request Header.

💁🏻‍♀️ Create an account **[here](https://scrappy-twitter-api-client-xi.vercel.app/)** to get a DID token.

Great! Now that you’ve got a DID token, you can pass it into the Postman Collection’s HTTP Authorization request header as a Bearer Token and be able to send a `Create a Tweet` or `Delete a Tweet` request.

![Postman Collection] (https://i.imgur.com/VjasF9i.png, "Magic Auth x Webflow")

Keep reading if you want to learn how we secured this Go-backed Scrappy Twitter API with the Magic Admin SDK for Go. 🪄🔐

# A High-level View <a name="high-level"></a>

Here are the building blocks of this project and how each part connects to one another.

1. [Client](https://github.com/seemcat/scrappy-twitter-api-client)

    This Next.js app authenticates the user and generates the DID token required to make POST or DELETE requests with the Scrappy Twitter API.

    ✨ **Noteworthy Package Dependencies**:
    * **[Magic SDK](https://www.google.com/url?q=https://docs.magic.link/client-sdk/web/get-started&sa=D&ust=1611100672262000&usg=AOvVaw0cRjSYqF0PYAWNC_GNYAVT)**: Allows users to sign up or log in.
    * **[SWR](https://github.com/vercel/swr)**: Lets us get user info using a hook.
    * **[@hapi/iron](https://github.com/hapijs/iron)**: Lets us encrypt the login cookie for more security.

2. [Server](https://github.com/seemcat/scrappy-twitter-api-server)

    This Go server is where all of the Scrappy Twitter API requests are handled. Once the user has generated a DID token from the client side, they can pass it into their Request Header as a Bearer token to hit protected endpoints. 


    ✨ **API routes**:
    * POST a tweet (protected): [http://localhost:8080/tweet](http://localhost:8000/tweet) 
    * GET all tweets (unprotected): [http://localhost:8080/tweets](http://localhost:8000/tweets) 
    * GET a single tweet (unprotected): [http://localhost:8080/tweet/1](http://localhost:8000/tweet/1) 
    * DELETE a tweet (protected): [http://localhost:8080/tweet/2](http://localhost:8000/tweet/2)

    ✨ **Noteworthy Packages**:
    * **[gorilla/handlers](https://github.com/gorilla/handlers)**: Lets us enable CORS.
    * **[gorilla/mux](https://github.com/gorilla/mux)**: Lets us build a powerful HTTP router and URL matcher.
    * **[magic-admin-go/client](https://docs.magic.link/admin-sdk/go/get-started#creating-an-sdk-client-instance)**: Lets us instantiate the Magic Admin SDK for Go.
    * **[magic-admin-go/token](https://docs.magic.link/admin-sdk/go/get-started#creating-a-token-instance)**: Lets us create a [Token instance](https://docs.magic.link/admin-sdk/go/api-reference#token-resource).

In this article, we’ll only be focusing on the Server’s code to show you how we secured the Go Rest API.

# Getting Started <a name="getting-started"></a>
## Prerequisites ✅ <a name="prerequisites"></a>
- [Go](https://golang.org/doc/install)
- [Postman](https://www.postman.com/downloads/)

## Magic 🦄 <a name="magic"></a>
1. Sign up for an account on [Magic](https://magic.link/).
2. Create an app.
3. Keep this Magic tab open. You’ll need both of your app’s Test Publishable and Secret keys soon.

**Note**: Test API keys are always allowed to be used on localhost. For added security, you can specify the URLs that are allowed to use your live API keys in Magic's [Dashboard](https://dashboard.magic.link) Settings. Doing so will block your Live API keys from working anywhere except the URLs in your whitelisted domains.

## Server 💾 <a name="server"></a>
1. `git clone https://github.com/magiclabs/scrappy-twitter-api-server` 
2. `cd scrappy-twitter-api-server`
3. `mv .env.example .env`
4. Go back to the Magic tab to copy your app’s Test Secret Key and paste it as the value for `MAGIC_TEST_SECRET_KEY` in `.env`:

    ```javascript

    MAGIC_TEST_SECRET_KEY=sk_test_XXXXXXXXXX

    ```
5. Run all `.go` files with `go run .`

## Client 🖥 <a name="client"></a>
1. `git clone https://github.com/magiclabs/scrappy-twitter-api-client` 
2. `cd scrappy-twitter-api-client`
3. `mv .env.local.example .env.local`
4. Populate `.env.local` with the correct Test keys from your Magic app:

    ```javascript
    NEXT_PUBLIC_MAGIC_TEST_PUBLISHABLE_KEY=pk_test_XXXXX
    NEXT_PUBLIC_MAGIC_TEST_SECRET_KEY=sk_test_XXXXX
    NEXT_PUBLIC_HAPI_IRON_SECRET=this-is-a-secret-value-with-at-least-32-characters
    ```

    **Note:** The `NEXT_PUBLIC_HAPI_IRON_SECRET` is needed by @hapi/iron to encrypt an object. Feel free to leave the default value as is while in DEV.

5. Install package dependencies: `yarn`
6. Start the Next.js production server: `yarn dev`

## Postman 📫 <a name="postman"></a>
1. Import the `DEV` version of the Scrappy Twitter API Postman Collection:

    [![Run in Postman](https://run.pstmn.io/button.svg)](https://god.postman.co/run-collection/1aa913713995cb16bb70)

2. Generate a DID token on the Client you just started up.

    **Note**: When you log in from the Client side, the Magic Client SDK generates a DID token which is then converted to an [ID token](https://docs.magic.link/client-sdk/web/api-reference#getidtoken) so that it has a longer lifespan (8 hours).

3. Pass this DID token as a Bearer token into the collection’s HTTP Authorization request header. 

Awesome! Now that you have your own local Next.js client and Go server running, let's dive into the server's code.

# The Scrappy Twitter Go Rest API <a name="go-api"></a>
## File Structure <a name="file-structure"></a>
This is a simplified view of the [Go server](https://github.com/seemcat/scrappy-twitter-api-server)'s file structure:

```txt
├── README.md
├── .env
├── main.go
├── structs.go
├── handlers.go
```

## A Local Database <a name="local-db"></a>
To keep things simple, when you create or delete a tweet, instead of updating an external database, the `Tweets` array that’s globally defined and initialized in `structs.go` is updated accordingly.

```go
// Tweet is struct or data type with an Id, Copy and Author
type Tweet struct {
   ID     string `json:"ID"`
   Copy   string `json:"Copy"`
   Author string `json:"Author"`
}

// Tweets is an array of Tweet structs
var Tweets []Tweet
```

And when you get all tweets, or a single tweet, the same `Tweets` array is sent back to the client.

## The Routes and Handlers <a name="routes-handlers"></a>
In summary, this Scrappy Twitter Go Rest API has 4 key routes that are defined in `main.go`’s `handleRequests` function:

1. **GET** "/tweets" *to get all tweets*
    ```go
    myRouter.HandleFunc("/tweets", returnAllTweets)
    ```

2. **DELETE** "/tweet/{id}" *to delete a tweet*
    ```go
    myRouter.HandleFunc("/tweet/{id}", deleteATweet).Methods("DELETE")
    ```

3. **GET** "/tweet/{i}" *to get a single tweet*
    ```go
    myRouter.HandleFunc("/tweet/{id}", returnSingleTweet)
    ```

4. **POST** "/tweet" *to create a tweet*
    ```go
    myRouter.HandleFunc("/tweet", createATweet).Methods("POST")

    ```

**Note**: The POST and DELETE routes are currently unprotected. Move to the [next section](#secure-go-api) to see how we can use a DID token to protect them.

As you can see, each of these routes have their own handlers to properly respond to requests. These handlers are defined in `handlers.go`:

1. GET "/tweets" => `returnAllTweets()`
    ```go
    // Returns ALL tweets ✨
    func returnAllTweets(w http.ResponseWriter, r *http.Request) {
       fmt.Println("Endpoint Hit: returnAllTweets")
       json.NewEncoder(w).Encode(Tweets)
    }
    ```
2. DELETE "/tweet/{id}" => `deleteATweet()`
    ```go
    // Deletes a tweet ✨
    func deleteATweet(w http.ResponseWriter, r *http.Request) {
       fmt.Println("Endpoint Hit: deleteATweet")

       // Parse the path parameters
       vars := mux.Vars(r)

       // Extract the `id` of the tweet we wish to delete
       id := vars["id"]

       // Loop through all our tweets
       for index, tweet := range Tweets {

           /*
               Checks whether or not our id path
               parameter matches one of our tweets.
           */
           if tweet.ID == id {

               // Updates our Tweets array to remove the tweet
               Tweets = append(Tweets[:index], Tweets[index+1:]...)
           }
       }

       w.Write([]byte("Yay! Tweet has been DELETED."))
    }
    ```
3. GET "/tweet/{i}" => `returnSingleTweet()`
    ```go
    // Returns a SINGLE tweet ✨
    func returnSingleTweet(w http.ResponseWriter, r *http.Request) {
       fmt.Println("Endpoint Hit: returnSingleTweet")
       vars := mux.Vars(r)
       key := vars["id"]

       /*
           Loop over all of our Tweets
           If the tweet.Id equals the key we pass in
           Return the tweet encoded as JSON
       */
       for _, tweet := range Tweets {
           if tweet.ID == key {
               json.NewEncoder(w).Encode(tweet)
           }
       }
    }
    ```
4. POST "/tweet" => `createATweet()`
    ```go
    // Creates a tweet ✨
    func createATweet(w http.ResponseWriter, r *http.Request) {
       fmt.Println("Endpoint Hit: createATweet")
       /*
           Get the body of our POST request
           Unmarshal this into a new Tweet struct
       */
       reqBody, _ := ioutil.ReadAll(r.Body)
       var tweet Tweet
       json.Unmarshal(reqBody, &tweet)

       /*
           Update our global Tweets array to include
           Our new Tweet
       */
       Tweets = append(Tweets, tweet)
       json.NewEncoder(w).Encode(tweet)

       w.Write([]byte("Yay! Tweet CREATED."))
    }
    ```

# Securing the Go Rest API with Magic Admin SDK <a name="secure-go-api"></a>

Now it’s time to show you how to protect the **DELETE** "/tweet/{id}" and **POST** "/tweet" routes, such that only authenticated users are able to create a tweet and only the author of a specific tweet is allowed to delete it.

## Magic Setup <a name="magic-setup"></a>
1. Get the Go Magic Admin SDK package:
    `go get github.com/magiclabs/magic-admin-go`

2. Configure the Magic Admin SDK in `handlers.go`:
    1. Import the following packages:
           ```go
           "github.com/joho/godotenv"
           "github.com/magiclabs/magic-admin-go"
           "github.com/magiclabs/magic-admin-go/client"
           "github.com/magiclabs/magic-admin-go/token"
           ```
    2. Load the `.env` file and get the Test Secret Key:
        ```go
        // Load .env file from given path
        var err = godotenv.Load(".env")

        // Get env variables
        var magicSecretKey = os.Getenv("MAGIC_TEST_SECRET_KEY")
        ```
    3. Instantiate Magic:
        ```go
        var magicSDK = client.New(magicSecretKey, magic.NewDefaultClient())
        ```

## Magic Admin SDK for Go <a name="magic-sdk-go"></a>
In order to protect the routes to POST or DELETE a tweet, we’ll be creating a Gorilla Mux middleware to check whether or not the user is authorized to make requests to these endpoints.

💡 *You can think of a middleware as reusable code for HTTP request handling.*

### checkBearerToken() <a name="check-bearer-token"></a>

Let’s call this middleware `checkBearerToken()` and define it in `handlers.go`. 

To implement the middleware behavior, we’ll be using chainable closures. This way, we could wrap each handler with a `checkBearerToken` middleware. 

Here’s the initial look of our function:
```go
func checkBearerToken(next httpHandlerFunc) httpHandlerFunc {
   return func(res http.ResponseWriter, req *http.Request) {
      /* More code is coming! */
      next(res, req)
   }
}
```

💁🏻‍♀️ Now let’s update `checkBearerToken` to make sure the DID token exists in the HTTP Header Request. If it does, store the value into a variable called `didToken`:

```go
func checkBearerToken(next httpHandlerFunc) httpHandlerFunc {
   fmt.Println("Middleware Hit: checkBearerToken")
   return func(res http.ResponseWriter, req *http.Request) {

       // Check whether or not DIDT exists in HTTP Header Request
       if !strings.HasPrefix(req.Header.Get("Authorization"), authBearer) {
           fmt.Fprintf(res, "Bearer token is required")
           return
       }

       // Retrieve DIDT token from HTTP Header Request
       didToken := req.Header.Get("Authorization")[len(authBearer)+1:]

      /* More code is coming! */
      next(res, req)
   }
}
```

Cool. Now that we’ve got a DID token, we can use it to create an instance of a [Token](https://docs.magic.link/admin-sdk/go/api-reference#token-resource). The Token resource provides methods to interact with the [DID Token](https://docs.magic.link/decentralized-id). We’ll need to interact with the DID Token to get the authenticated user’s information. But first, we’ll need to validate it.

💁🏻‍♀️ Update `checkBearerToken` to include this code:
```go
func checkBearerToken(next httpHandlerFunc) httpHandlerFunc {
   fmt.Println("Middleware Hit: checkBearerToken")
   return func(res http.ResponseWriter, req *http.Request) {

       // Check whether or not DIDT exists in HTTP Header Request
       if !strings.HasPrefix(req.Header.Get("Authorization"), authBearer) {
           fmt.Fprintf(res, "Bearer token is required")
           return
       }

       // Retrieve DIDT token from HTTP Header Request
       didToken := req.Header.Get("Authorization")[len(authBearer)+1:]

       // Create a Token instance to interact with the DID token
       tk, err := token.NewToken(didToken)
       if err != nil {
           fmt.Fprintf(res, "Malformed DID token error: %s", err.Error())
           res.Write([]byte(err.Error()))
           return
       }

       // Validate the Token instance before using it
       if err := tk.Validate(); err != nil {
           fmt.Fprintf(res, "DID token failed validation: %s", err.Error())
           return
       }

      /* More code is coming! */
      next(res, req)
   }
}
```

Now that we’ve validated the Token (`tk`), we can call [tk.GetIssuer()](https://docs.magic.link/admin-sdk/go/api-reference#getissuer) to retrieve the `iss`; a Decentralized ID of the Magic user who generated the DID Token. We’ll be passing `iss` into [magicSDK.User.GetMetadataByIssuer](https://docs.magic.link/admin-sdk/go/api-reference#getmetadatabyissuer) to get the authenticated user’s information.

💁🏻‍♀️ Update `checkBearerToken` again:
```go
func checkBearerToken(next httpHandlerFunc) httpHandlerFunc {
   fmt.Println("Middleware Hit: checkBearerToken")
   return func(res http.ResponseWriter, req *http.Request) {

       // Check whether or not DIDT exists in HTTP Header Request
       if !strings.HasPrefix(req.Header.Get("Authorization"), authBearer) {
           fmt.Fprintf(res, "Bearer token is required")
           return
       }

       // Retrieve DIDT token from HTTP Header Request
       didToken := req.Header.Get("Authorization")[len(authBearer)+1:]

       // Create a Token instance to interact with the DID token
       tk, err := token.NewToken(didToken)
       if err != nil {
           fmt.Fprintf(res, "Malformed DID token error: %s", err.Error())
           res.Write([]byte(err.Error()))
           return
       }

       // Validate the Token instance before using it
       if err := tk.Validate(); err != nil {
           fmt.Fprintf(res, "DID token failed validation: %s", err.Error())
           return
       }

       // Get the user's information
       userInfo, err := magicSDK.User.GetMetadataByIssuer(tk.GetIssuer())
       if err != nil {
           fmt.Fprintf(res, "Error when calling GetMetadataByIssuer: %s", err.Error())
           return
       }

      /* More code is coming! */
      next(res, req)
   }
}
```

Awesome. If the request was able to make it past this point, then we can be assured that it was an authenticated request. All we need to do now is pass the user’s information to the handler the middleware is chained to. We’ll be using [Go's Package context](https://golang.org/pkg/context/) to achieve this. 

💡 *In short, the Package `context` will make it easy for us to store objects as context values and pass them to all handlers that are chained to our `checkBearerToken` middleware.*

Make sure to import the `"context"` in `handlers.go`.

Then create a `userInfoKey` at the top of `handlers.go` (we'll be passing `userInfoKey` into `context.WithValue` soon):
```go
type key string
const userInfoKey key = "userInfo"
```

💁🏻‍♀️ Update `checkBearerToken` one last time to use context values to store the user’s information:
```go
func checkBearerToken(next httpHandlerFunc) httpHandlerFunc {
   fmt.Println("Middleware Hit: checkBearerToken")
   return func(res http.ResponseWriter, req *http.Request) {

       // Check whether or not DIDT exists in HTTP Header Request
       if !strings.HasPrefix(req.Header.Get("Authorization"), authBearer) {
           fmt.Fprintf(res, "Bearer token is required")
           return
       }

       // Retrieve DIDT token from HTTP Header Request
       didToken := req.Header.Get("Authorization")[len(authBearer)+1:]

       // Create a Token instance to interact with the DID token
       tk, err := token.NewToken(didToken)
       if err != nil {
           fmt.Fprintf(res, "Malformed DID token error: %s", err.Error())
           res.Write([]byte(err.Error()))
           return
       }

       // Validate the Token instance before using it
       if err := tk.Validate(); err != nil {
           fmt.Fprintf(res, "DID token failed validation: %s", err.Error())
           return
       }

       // Get the the user's information
       userInfo, err := magicSDK.User.GetMetadataByIssuer(tk.GetIssuer())
       if err != nil {
           fmt.Fprintf(res, "Error when calling GetMetadataByIssuer: %s", err.Error())
           return
       }

       // Use context values to store user's info
       ctx := context.WithValue(req.Context(), userInfoKey, userInfo)
       req = req.WithContext(ctx)
       next(res, req)
   }
}
```

Looks good! Writing `checkBearerToken()` is the bulk of the work needed to Magic-ally protect the routes for posting or deleting a tweet. 

All that’s left to do now is:
1. Wrap `createATweet` and `deleteATweet` handlers in `main.go`’s `handleRequests` function with this `checkBearerToken` middleware.
2. Update these handlers to get the user’s information from the context values, and then tag each tweet with the user’s email so that authors are able to delete their own tweet.

### handleRequests() <a name="handle-requests"></a>

All we did in `main.go`’s `handleRequest()` is wrap the `deleteATweet` and `createATweet` handlers with the `checkBearerToken` middleware function.

```go
func handleRequests() {

   /* REST OF THE CODE IS OMITTED */

   // Delete a tweet ✨
   myRouter.HandleFunc("/tweet/{id}", checkBearerToken(deleteATweet)).Methods("DELETE")

   // Create a tweet ✨
   myRouter.HandleFunc("/tweet", checkBearerToken(createATweet)).Methods("POST")


   /* REST OF THE CODE IS OMITTED */
}
```

### createATweet() <a name="create-tweet"></a>
To access the key-value pairs in `userInfo` object, we first needed to get the object by calling `r.Context().Value(userInfoKey)` with the `userInfoKey` we defined earlier, and then we needed to assert two things:

1. `userInfo` is not nil 
2. the value stored in `userInfo` is of type `*magic.UserInfo`

Both of these assertions are done with `userInfo.(*magic.UserInfo)`.

```go
// Creates a tweet ✨
func createATweet(w http.ResponseWriter, r *http.Request) {

   fmt.Println("Endpoint Hit: createATweet")

   // Get the authenticated author's info from context values
   userInfo := r.Context().Value(userInfoKey)
   userInfoMap := userInfo.(*magic.UserInfo)

   /*
       Get the body of our POST request
       Unmarshal this into a new Tweet struct
       Add the authenticated author to the tweet
   */
   reqBody, _ := ioutil.ReadAll(r.Body)
   var tweet Tweet
   json.Unmarshal(reqBody, &tweet)
   tweet.Author = userInfoMap.Email

   /*
       Update our global Tweets array to include
       Our new Tweet
   */
   Tweets = append(Tweets, tweet)
   json.NewEncoder(w).Encode(tweet)

   fmt.Println("Yay! Tweet CREATED.")
}
```

As you can see, we’ve also added the authenticated author to the tweet.

### deleteATweet() <a name="delete-tweet"></a>
Now that we know which author created the tweet, we’ll be able to only allow that author to delete it.
```go
// Deletes a tweet ✨
func deleteATweet(w http.ResponseWriter, r *http.Request) {
   fmt.Println("Endpoint Hit: deleteATweet")

   // Get the authenticated author's info from context values
   userInfo := r.Context().Value(userInfoKey)
   userInfoMap := userInfo.(*magic.UserInfo)

   // Parse the path parameters
   vars := mux.Vars(r)
   // Extract the `id` of the tweet we wish to delete
   id := vars["id"]

   // Loop through all our tweets
   for index, tweet := range Tweets {

       /*
           Checks whether or not our id and author path
           parameter matches one of our tweets.
       */
       if (tweet.ID == id) && (tweet.Author == userInfoMap.Email) {

           // Updates our Tweets array to remove the tweet
           Tweets = append(Tweets[:index], Tweets[index+1:]...)
           w.Write([]byte("Yay! Tweet has been DELETED."))
           return
       }
   }

   w.Write([]byte("Ooh. You can't delete someone else's tweet."))
}
```
---
Yay! Now you know how to protect Go RESTful API routes 🎉. Feel free to create and delete your own tweet, and also try to delete our default tweet in the Postman Collection to test our protected endpoints.

I hope you enjoyed how quick and easy it was to secure the Go-backed Scrappy Twitter API with the Magic Admin SDK for Go. Next time you want to build a Go REST API for authenticated users, this guide will always have your back.

Btw, if you run into any issues, feel free to reach out [@seemcat](https://twitter.com/seemcat).

Till next time 🙋🏻‍♀️.