diff --git a/sources/tech/20180429 Passwordless Auth- Client.md b/sources/tech/20180429 Passwordless Auth- Client.md
new file mode 100644
index 0000000000..06e4464709
--- /dev/null
+++ b/sources/tech/20180429 Passwordless Auth- Client.md
@@ -0,0 +1,338 @@
+Passwordless Auth: Client
+======
+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`:
+```
+router.Handle("GET", "/js/", http.FileServer(http.Dir("static")))
+router.HandleFunc("GET", "/...", serveFile("static/index.html"))
+
+```
+
+This serves files under `static/js`, and `static/index.html` is served for everything else.
+
+You can use your own server apart, but you’ll have to enable [CORS][3] on the server.
+
+### HTML
+
+Let’s see that `static/index.html`.
+```
+
+
+
+
+
+ Passwordless Demo
+
+
+
+
+
+
+```
+
+Single page application left all the rendering to JavaScript, so we have an empty body and a `main.js` file.
+
+I’ll user the Router from the [last post][2].
+
+### Rendering
+
+Now, create a `static/js/main.js` file with the following content:
+```
+import Router from 'https://unpkg.com/@nicolasparada/router'
+import { isAuthenticated } from './auth.js'
+
+const router = new Router()
+
+router.handle('/', guard(view('home')))
+router.handle('/callback', view('callback'))
+router.handle(/^\//, view('not-found'))
+
+router.install(async resultPromise => {
+ document.body.innerHTML = ''
+ document.body.appendChild(await resultPromise)
+})
+
+function view(name) {
+ return (...args) => import(`/js/pages/${name}-page.js`)
+ .then(m => m.default(...args))
+}
+
+function guard(fn1, fn2 = view('welcome')) {
+ return (...args) => isAuthenticated()
+ ? fn1(...args)
+ : fn2(...args)
+}
+
+```
+
+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:
+```
+export function getAuthUser() {
+ const authUserItem = localStorage.getItem('auth_user')
+ const expiresAtItem = localStorage.getItem('expires_at')
+
+ if (authUserItem !== null && expiresAtItem !== null) {
+ const expiresAt = new Date(expiresAtItem)
+
+ if (!isNaN(expiresAt.valueOf()) && expiresAt > new Date()) {
+ try {
+ return JSON.parse(authUserItem)
+ } catch (_) { }
+ }
+ }
+
+ return null
+}
+
+export function isAuthenticated() {
+ return localStorage.getItem('jwt') !== null && getAuthUser() !== null
+}
+
+```
+
+When someone login, we save the JSON web token, expiration date of it and the current authenticated user on `localStorage`. This module uses that.
+
+ * `getAuthUser()` gets the authenticated user from `localStorage` making sure the JSON Web Token hasn’t expired yet.
+ * `isAuthenticated()` makes use of the previous function to check whether it doesn’t return `null`.
+
+
+
+### Fetch
+
+Before continuing with the pages, I’ll code some HTTP utilities to work with the server API.
+
+Let’s create a `static/js/http.js` file with the following content:
+```
+import { isAuthenticated } from './auth.js'
+
+function get(url, headers) {
+ return fetch(url, {
+ headers: Object.assign(getAuthHeader(), headers),
+ }).then(handleResponse)
+}
+
+function post(url, body, headers) {
+ return fetch(url, {
+ method: 'POST',
+ headers: Object.assign(getAuthHeader(), { 'content-type': 'application/json' }, headers),
+ body: JSON.stringify(body),
+ }).then(handleResponse)
+}
+
+function getAuthHeader() {
+ return isAuthenticated()
+ ? { authorization: `Bearer ${localStorage.getItem('jwt')}` }
+ : {}
+}
+
+export async function handleResponse(res) {
+ 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 ` 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:
+```
+const template = document.createElement('template')
+template.innerHTML = `
+
Passwordless Demo
+
Access
+
+`
+
+export default function welcomePage() {
+ const page = template.content.cloneNode(true)
+
+ page.getElementById('access-form')
+ .addEventListener('submit', onAccessFormSubmit)
+
+ return page
+}
+
+```
+
+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:
+```
+import http from '../http.js'
+
+const template = document.createElement('template')
+template.innerHTML = `
+
Authenticating you 👀
+`
+
+export default function callbackPage() {
+ const page = template.content.cloneNode(true)
+
+ const hash = location.hash.substr(1)
+ const fragment = new URLSearchParams(hash)
+ for (const [k, v] of fragment.entries()) {
+ fragment.set(decodeURIComponent(k), decodeURIComponent(v))
+ }
+ const jwt = fragment.get('jwt')
+ const expiresAt = fragment.get('expires_at')
+
+ http.get('/api/auth_user', { authorization: `Bearer ${jwt}` })
+ .then(res => res.body)
+ .then(authUser => {
+ localStorage.setItem('jwt', jwt)
+ localStorage.setItem('auth_user', JSON.stringify(authUser))
+ localStorage.setItem('expires_at', expiresAt)
+
+ location.replace('/')
+ })
+ .catch(console.error)
+
+ return page
+}
+
+```
+
+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:
+```
+import { getAuthUser } from '../auth.js'
+
+export default function homePage() {
+ const authUser = getAuthUser()
+
+ const template = document.createElement('template')
+ template.innerHTML = `
+
Passwordless Demo
+
Welcome back, ${authUser.username} 👋
+
+ `
+
+ const page = template.content
+
+ page.getElementById('logout-button')
+ .addEventListener('click', logout)
+
+ return page
+}
+
+function logout() {
+ localStorage.clear()
+ location.reload()
+}
+
+```
+
+This page greets the authenticated user and also has a logout button. The `logout()` function just clears `localStorage` and reloads the page.
+
+There is it. I bet you already saw the [demo][4] before. Also, the source code is in the same [repository][5].
+
+👋👋👋
+
+--------------------------------------------------------------------------------
+
+via: https://nicolasparada.netlify.com/posts/passwordless-auth-client/
+
+作者:[Nicolás Parada][a]
+选题:[lujun9972](https://github.com/lujun9972)
+译者:[译者ID](https://github.com/译者ID)
+校对:[校对者ID](https://github.com/校对者ID)
+
+本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
+
+[a]:https://nicolasparada.netlify.com/
+[1]:https://nicolasparada.netlify.com/posts/passwordless-auth-server/
+[2]:https://nicolasparada.netlify.com/posts/javascript-client-router/
+[3]:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
+[4]:https://go-passwordless-demo.herokuapp.com/
+[5]:https://github.com/nicolasparada/go-passwordless-demo