Time to continue with the [passwordless auth][1] posts. Previously, we wrote an HTTP service in Go that provided with a passwordless authentication API. Now, we are gonna code a JavaScript client for it.
We’ll go with a single page application (SPA) using the technique I showed [here][2]. Read it first if you haven’t yet.
For the root URL (`/`) we’ll show two different pages depending on the auth state: a page with an access form or a page greeting the authenticated user. Another page is for the auth callback redirect.
### Serving
I’ll serve the client with the same Go server, so let’s add some routes to the previous `main.go`:
Differing from the last post, we implement an `isAuthenticated()` function and a `guard()` function that uses it to render one or another page. So when a user visits `/` it will show the home or welcome page whether the user is authenticated or not.
### Auth
Now, let’s write that `isAuthenticated()` function. Create a `static/js/auth.js` file with the following content:
const body = await res.clone().json().catch(() => res.text())
const response = {
url: res.url,
statusCode: res.status,
statusText: res.statusText,
headers: res.headers,
body,
}
if (!res.ok) throw Object.assign(
new Error(body.message || body || res.statusText),
response
)
return response
}
export default {
get,
post,
}
```
This module exports `get()` and `post()` functions. They are wrappers around the `fetch` API. Both functions inject an `Authorization: Bearer <token_here>` header to the request when the user is authenticated; that way the server can authenticate us.
### Welcome Page
Let’s move to the welcome page. Create a `static/js/pages/welcome-page.js` file with the following content:
This page uses an `HTMLTemplateElement` for the view. It is just a simple form to enter the user’s email.
To not make this boring, I’ll skip error handling and just log them to console.
Now, let’s code that `onAccessFormSubmit()` function.
```
import http from '../http.js'
function onAccessFormSubmit(ev) {
ev.preventDefault()
const form = ev.currentTarget
const input = form.querySelector('input')
const email = input.value
sendMagicLink(email).catch(err => {
console.error(err)
if (err.statusCode === 404 && wantToCreateAccount()) {
runCreateUserProgram(email)
}
})
}
function sendMagicLink(email) {
return http.post('/api/passwordless/start', {
email,
redirectUri: location.origin + '/callback',
}).then(() => {
alert('Magic link sent. Go check your email inbox.')
})
}
function wantToCreateAccount() {
return prompt('No user found. Do you want to create an account?')
}
```
It does a `POST` request to `/api/passwordless/start` with the email and redirectUri in the body. In case it returns with `404 Not Found` status code, we’ll create a user.
```
function runCreateUserProgram(email) {
const username = prompt("Enter username")
if (username === null) return
http.post('/api/users', { email, username })
.then(res => res.body)
.then(user => sendMagicLink(user.email))
.catch(console.error)
}
```
The user creation program, first, ask for username and does a `POST` request to `/api/users` with the email and username in the body. On success, it sends a magic link for the user created.
### Callback Page
That was all the functionality for the access form, let’s move to the callback page. Create a `static/js/pages/callback-page.js` file with the following content:
To remember… when clicking the magic link, we go to `/api/passwordless/verify_redirect` which redirect us to the redirect URI we pass (`/callback`) with the JWT and expiration date in the URL hash.
The callback page decodes the hash from the URL to extract those parameters to do a `GET` request to `/api/auth_user` with the JWT saving all the data to `localStorage`. Finally, it just redirects to home.
### Home Page
Create a `static/pages/home-page.js` file with the following content: