<p>Today, let's have a look at how you can use <a href="http://jwt.io">JWT Authentication</a> in <a href="http://golang.org">Go</a>. We are going to use it in combination with <a href="http://echo.labstack.com">Labstack Echo</a> (my preferred tool for creating web servers with <a href="http://golang.org">Go</a>).</p>
<p>Let's define a simple webserver supporting <a href="http://jwt.io">JWT Authentication</a> like this:</p>
<div class="highlight"><pre><span></span><span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="p">(</span>
<span class="s">"net/http"</span>
<span class="s">"time"</span>
<span class="s">"github.com/dgrijalva/jwt-go"</span>
<span class="s">"github.com/labstack/echo/v4"</span>
<span class="s">"github.com/labstack/echo/v4/middleware"</span>
<span class="s">"github.com/pieterclaerhout/go-log"</span>
<span class="p">)</span>
<span class="kd">const</span> <span class="nx">secret</span> <span class="p">=</span> <span class="s">"secret"</span>
<span class="kd">type</span> <span class="nx">jwtCustomClaims</span> <span class="kd">struct</span> <span class="p">{</span>
<span class="nx">Name</span> <span class="kt">string</span> <span class="s">`json:"name"`</span>
<span class="nx">UUID</span> <span class="kt">string</span> <span class="s">`json:"uuid"`</span>
<span class="nx">Admin</span> <span class="kt">bool</span> <span class="s">`json:"admin"`</span>
<span class="nx">jwt</span><span class="p">.</span><span class="nx">StandardClaims</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">login</span><span class="p">(</span><span class="nx">c</span> <span class="nx">echo</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
<span class="nx">username</span> <span class="o">:=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">FormValue</span><span class="p">(</span><span class="s">"username"</span><span class="p">)</span>
<span class="nx">password</span> <span class="o">:=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">FormValue</span><span class="p">(</span><span class="s">"password"</span><span class="p">)</span>
<span class="k">if</span> <span class="nx">username</span> <span class="o">!=</span> <span class="s">"pieter"</span> <span class="o">||</span> <span class="nx">password</span> <span class="o">!=</span> <span class="s">"claerhout"</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">echo</span><span class="p">.</span><span class="nx">ErrUnauthorized</span>
<span class="p">}</span>
<span class="nx">claims</span> <span class="o">:=</span> <span class="o">&</span><span class="nx">jwtCustomClaims</span><span class="p">{</span>
<span class="nx">Name</span><span class="p">:</span> <span class="s">"Pieter Claerhout"</span><span class="p">,</span>
<span class="nx">UUID</span><span class="p">:</span> <span class="s">"9E98C454-C7AC-4330-B2EF-983765E00547"</span><span class="p">,</span>
<span class="nx">Admin</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nx">StandardClaims</span><span class="p">:</span> <span class="nx">jwt</span><span class="p">.</span><span class="nx">StandardClaims</span><span class="p">{</span>
<span class="nx">ExpiresAt</span><span class="p">:</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Now</span><span class="p">().</span><span class="nx">Add</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Hour</span> <span class="o">*</span> <span class="mi">72</span><span class="p">).</span><span class="nx">Unix</span><span class="p">(),</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="nx">token</span> <span class="o">:=</span> <span class="nx">jwt</span><span class="p">.</span><span class="nx">NewWithClaims</span><span class="p">(</span><span class="nx">jwt</span><span class="p">.</span><span class="nx">SigningMethodHS256</span><span class="p">,</span> <span class="nx">claims</span><span class="p">)</span>
<span class="nx">t</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">token</span><span class="p">.</span><span class="nx">SignedString</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">secret</span><span class="p">))</span>
<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">err</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">JSON</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">,</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span>
<span class="s">"token"</span><span class="p">:</span> <span class="nx">t</span><span class="p">,</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">accessible</span><span class="p">(</span><span class="nx">c</span> <span class="nx">echo</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">String</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">,</span> <span class="s">"Accessible"</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">restricted</span><span class="p">(</span><span class="nx">c</span> <span class="nx">echo</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
<span class="nx">user</span> <span class="o">:=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">Get</span><span class="p">(</span><span class="s">"user"</span><span class="p">).(</span><span class="o">*</span><span class="nx">jwt</span><span class="p">.</span><span class="nx">Token</span><span class="p">)</span>
<span class="nx">claims</span> <span class="o">:=</span> <span class="nx">user</span><span class="p">.</span><span class="nx">Claims</span><span class="p">.(</span><span class="o">*</span><span class="nx">jwtCustomClaims</span><span class="p">)</span>
<span class="nx">log</span><span class="p">.</span><span class="nx">InfoDump</span><span class="p">(</span><span class="nx">claims</span><span class="p">,</span> <span class="s">"claims"</span><span class="p">)</span>
<span class="nx">name</span> <span class="o">:=</span> <span class="nx">claims</span><span class="p">.</span><span class="nx">Name</span>
<span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">String</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">,</span> <span class="s">"Welcome "</span><span class="o">+</span><span class="nx">name</span><span class="o">+</span><span class="s">"!"</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">e</span> <span class="o">:=</span> <span class="nx">echo</span><span class="p">.</span><span class="nx">New</span><span class="p">()</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">HideBanner</span> <span class="p">=</span> <span class="kc">true</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">HidePort</span> <span class="p">=</span> <span class="kc">true</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">Use</span><span class="p">(</span><span class="nx">middleware</span><span class="p">.</span><span class="nx">Logger</span><span class="p">())</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">Use</span><span class="p">(</span><span class="nx">middleware</span><span class="p">.</span><span class="nx">Recover</span><span class="p">())</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">POST</span><span class="p">(</span><span class="s">"/login"</span><span class="p">,</span> <span class="nx">login</span><span class="p">)</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">GET</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="nx">accessible</span><span class="p">)</span>
<span class="nx">r</span> <span class="o">:=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">Group</span><span class="p">(</span><span class="s">"/restricted"</span><span class="p">)</span>
<span class="nx">config</span> <span class="o">:=</span> <span class="nx">middleware</span><span class="p">.</span><span class="nx">JWTConfig</span><span class="p">{</span>
<span class="nx">Claims</span><span class="p">:</span> <span class="o">&</span><span class="nx">jwtCustomClaims</span><span class="p">{},</span>
<span class="nx">SigningKey</span><span class="p">:</span> <span class="p">[]</span><span class="nb">byte</span><span class="p">(</span><span class="s">"secret"</span><span class="p">),</span>
<span class="p">}</span>
<span class="nx">r</span><span class="p">.</span><span class="nx">Use</span><span class="p">(</span><span class="nx">middleware</span><span class="p">.</span><span class="nx">JWTWithConfig</span><span class="p">(</span><span class="nx">config</span><span class="p">))</span>
<span class="nx">r</span><span class="p">.</span><span class="nx">GET</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="nx">restricted</span><span class="p">)</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">Logger</span><span class="p">.</span><span class="nx">Fatal</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">Start</span><span class="p">(</span><span class="s">":8080"</span><span class="p">))</span>
<span class="p">}</span>
</pre></div>
<p>Looking at the code, there are several endpoints defined:</p>
<ul>
<li><code>/login</code>: can be used to get a JWT token based on your login credentials</li>
<li><code>/</code>: an endpoint which doesn't require authentication</li>
<li><code>/restricted</code>: an endpoint which does require a valid JWT authentication token</li>
</ul>
<p>The server will register itself on port <code>8080</code> and we're ready to go.</p>
<p>You can start the server like:</p>
<pre><code>$ go run server.go</code></pre>
<p>You can then use the <code>/login</code> endpoint to get a token:</p>
<pre><code>$ curl -s -X POST -d 'username=pieter' -d 'password=claerhout' http://localhost:8080/login
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiUGlldGVyIENsYWVyaG91dCIsInV1aWQiOiI5RTk4QzQ1NC1DN0FDLTQzMzAtQjJFRi05ODM3NjVFMDA1NDciLCJhZG1pbiI6dHJ1ZSwiZXhwIjoxNjEwMzgzNTU0fQ.JqJ4x2yPXOmxJB1n4GqpfKnIMyorX2zF7kbWbfchX4c"}</code></pre>
<p>You can then use this token to access the restricted URL:</p>
<pre><code>$ curl http://localhost:8080/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiUGlldGVyIENsYWVyaG91dCIsInV1aWQiOiI5RTk4QzQ1NC1DN0FDLTQzMzAtQjJFRi05ODM3NjVFMDA1NDciLCJhZG1pbiI6dHJ1ZSwiZXhwIjoxNjEwMzgzNTU0fQ.JqJ4x2yPXOmxJB1n4GqpfKnIMyorX2zF7kbWbfchX4c"
Welcome Pieter Claerhout!</code></pre>
<p>In addition, the server log will show you the contents of the authentication token:</p>
<pre><code>claims &main.jwtCustomClaims{
Name: "Pieter Claerhout",
UUID: "9E98C454-C7AC-4330-B2EF-983765E00547",
Admin: true,
StandardClaims: jwt.StandardClaims{
Audience: "",
ExpiresAt: 1610383647,
Id: "",
IssuedAt: 0,
Issuer: "",
NotBefore: 0,
Subject: "",
},
}</code></pre>
<p>The complete working implementation can be found <a href="https://github.com/pieterclaerhout/example-jwt">on GitHub</a>.</p>
Looking up a CNAME in Go
Gotcha with defer in Go
Parsing a key pair from a PEM file in Go
Using the Docker client from Go part 1
Embedding file with Go 1.16