This is part two of a three-part series tutorial that builds a small but complete JWT authentication solution for internal API (most concepts can also be applied to build JWT auth for public API).
- Part 1 — Public/secret key generation and storage
- Part 2 — Build a CLI to create/retrieve App object
- Part 3 — Build the JWT authentication middleware
Overview
In this part, we will build a mini CLI that helps us do the following:
- Retrieve the App (a set of public/secret key pair) object stored in our Postgres database by the public key.
- Generate a random key string to use as a master key.
- Create a new App object.
The CLI
We will use Cobra, a CLI framework package, to quickly generate the basics of our CLI. Let’s get
it.
go get -u github.com/spf13/cobra/cobra
We will make a cmd
package for all of our commands. Well, there’s only one for now. Let’s organize properly anyways. Here’s is our folder structure.
├── cmd
│ ├── app.go
│ ├── key.go
│ └── root.go
├── internal
│ └── app
│ ├── app.go
│ ├── create.go
│ └── get.go
└── main.go
In root.go
we combine all our commands under the one main command.
Root command will be called when you run go run main.go
As you can see, we already have two subcommands, keyCmd
and appCmd
. We will call Execute
in main.go
's main function later on.
🔑 Key Command
We will write keyCmd
first because it’s a lot simpler than appCmd
. This command will generate a 32-bits key and print it to the screen.
Cobra’s command type has a few properties that we can use. However, the main ones are Use
, Short
, Long
, and Run
. The first property, Use
, is crucial to identify how to call this command. In this case, we will call go run main.go key
to use this command. Short
and Long
properties are simply descriptions of the command in short form which will be displayed in the help section of the parent command or long description when we call --help
on the command itself. Run
is pretty self-explanatory. It runs the function that we passed in. The function should take 2 arguments which first is the command itself, and second is the arguments of the command.
We don’t use any arguments in this case since we always want to generate a 32-bits key and print it out.
🔐 App Command
This command will generate a credential key pairs, store them in the database and print out the keys. It can also fetch an App given its public key. It’s very useful when you have an internal API and want to give access to internal clients only. It can also be modified to work as an API endpoint.
There are a few things going on here. First, we have another “rootCmd”, which in this case, the app
command. This app
command will be the root for two commands create
and get
. There are a few new things here compare to key
command earlier. We use Args
property as a validation mechanism to enforce certain rules. In bothcreate
and get
, we want to have at least one argument. They are [name] and [public_key] respectively.
Second, we use a flag to take in a database URL connection. For simplicity, I defined var db string
as a package variable. However, please feel free to refactor it to be contained in a struct or so. It’s important that we know where to connect to the database so we’ll make --db
flag required. To bind a flag, we will call .StringVarP(destination *string, longFlag string, shortFlag string, defaultValue string, description string)
. As for .PersistentFlag()
, we make the flag persistent because we bind it on the app
command and not on create
or get
. A persistent flag will make the flag available even when you call child commands. Otherwise, flags are available under local scope only and you won’t be able to access var db
value. Here are the complete commands.
go run main.go app --db [dbURL] create [name]
go run main.go app --db [dbURL] get [public_key]
Now that we have the commands set up. Let’s dive into the handlers.
The handlers are small because we delegate most of the work to other services that will do the work for us. Those services are concerned with how to create an App
object given the information from the commands. These handlers are only responsible for calling those services. In addition, we will also have a data access layer that will take care of saving all of the information to the database.
Since the data access layer is quite long with SQL commands, I’d recommend you to take a look at the GitHub repo itself. It’s under goliauth/internal/app/app.go
. For now, we will focus on the two service functions that are used to create and get an App object.
💁🏻♂️ Services Layer
Welcome to our CLI service. Here we have the CreateApp
function that will… create an app, obviously. We begin by generating 2 random keys to be used as a public and secret key. Then, we encrypt the secret key and pass along the app’s name from our command to form an App
struct. When everything is properly formed, we call .Create
to instruct the data access layer to save all the information to the database given the URL.
Next, we have GetApp
function that will find our credentials given a public key. Using the public key, we can query the database and returns an App object that will contain an encrypted key. We will proceed to turn that key into bytes. Then, we decrypt that key an assign it back to the App object for reading. Finally, we will read it from the command and print it out.
Voila, that’s all there is to the CLI. Let’s see if it works
Full Github repo is here https://github.com/omnisyle/goliauth
Thank you for reading to the end!