Files
hackanooga.com/content/post/2024-01-10-roll-your-own-authenticator-app-with-keystonejs-and-react-pt-2.md
2025-02-23 17:00:05 -05:00

243 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
author: Mike Conrad
categories:
- Software Engineering
date: "2024-01-10T20:41:00Z"
tags:
- Blog Post
- GraphQL
- KeystoneJS
- NodeJS
- Prisma
- TypeScript
title: Roll your own authenticator app with KeystoneJS and React - pt 2
---
In part 1 of this series we built out a basic backend using KeystoneJS. In this part we will go ahead and start a new React frontend that will interact with our backend. We will be using Vite. Lets get started. Make sure you are in the `authenticator` folder and run the following:
```shell
$ yarn create vite@latest
yarn create v1.22.21
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Installed "create-vite@5.2.2" with binaries:
- create-vite
- cva
✔ Project name: … frontend
✔ Select a framework: React
✔ Select a variant: TypeScript
Scaffolding project in /home/Mike Conrad/projects/authenticator/frontend...
Done. Now run:
cd frontend
yarn
yarn dev
Done in 10.20s.
```
Lets go ahead and go into our frontend directory and get started:
```shell
$ cd frontend
$ yarn
yarn install v1.22.21
info No lockfile found.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
Done in 10.21s.
$ yarn dev
yarn run v1.22.21
$ vite
Port 5173 is in use, trying another one...
VITE v5.1.6 ready in 218 ms
➜ Local: http://localhost:5174/
➜ Network: use --host to expose
➜ press h + enter to show help
```
Next go ahead and open the project up in your IDE of choice. I prefer [VSCodium](https://vscodium.com/):
``` shell
codium frontend
```
Go ahead and open up `src/App.tsx` and remove all the boilerplate so it looks like this:
```typescript
import './App.css'
function App() {
return (
<>
</>
)
}
export default App
```
Lets start by building a card component that will display an individual token. Our goal is something that looks like this:
![](/wp-content/uploads/2024/03/ente-auth.webp)We will start by creating a Components folder with a Card component:
```shell
$ mkdir src/Components
$ touch src/Components/Card.tsx
```
Lets go ahead and make a couple updates, we will create this simple card component, add some dummy tokens and some basic styling.
```typescript
// src/App.tsx
import './App.css'
import Card from './Components/Card';
export interface IToken {
account: string;
issuer: string;
token: string;
}
function App() {
const tokens: IToken[] = [
{
account: 'enxoco@github.com',
issuer: 'Github',
token: 'AJFDLDAJKFK'
},
{
account: 'Mike Conrad@example.com',
issuer: 'Example.com',
token: 'KAJLFDJLKAFD'
}
]
return (
<>
<div className='cardWrapper'>
{tokens.map(token => <Card token={token} />)}
</div>
</>
)
}
export default App
```
```typescript
// src/Components/Card.tsx
import { IToken } from "../App"
function Card({ token }: { token: IToken }) {
return (
<>
<div className='card'>
<span>{token.issuer}</span>
<span>{token.account}</span>
<span>{token.token}</span>
</div>
</>
)
}
export default Card
```
```typescript
// src/index.css
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
background-color: #2c2c2c;
}
.cardWrapper {
display: flex;
}
.card {
padding: 2em;
min-width: 250px;
border: 1px solid;
margin: 10px;
background-color: #333333;
display: flex;
flex-direction: column;
align-items: baseline;
}
```
Now you should have something that looks like this:
![](/wp-content/uploads/2024/03/initial-token-frontend.webp)Alright, we have some of the boring stuff out of the way, now lets start making some magic. If you arent familiar with how TOTP tokens work, basically there is an Algorithm that generates them. I would encourage you to read the [RFC](https://datatracker.ietf.org/doc/html/rfc6238) for a detailed explanation. Basically it is an algorithm that generates a one time password using the current time as a source of uniqueness along with the secret key.
If we really wanted to we could implement this algorithm ourselves but thankfully there are some really simple libraries that do it for us. For our project we will be using one called [totp-generator](https://github.com/bellstrand/totp-generator). Lets go ahead and install it and check it out:
```shell
$ yarn add totp-generator
```
Now lets add it to our card component and see what happens. Using it is really simple. We just need to import it, instantiate a new `TokenGenerator` and pass it our Secret key:
```typescript
// src/Components/card.tsx
import { TOTP } from 'totp-generator';
---
function Card({ token }: { token: IToken }) {
const { otp, expires } = TOTP.generate(token.token)
return (
<>
<div className='card'>
<span>{token.issuer}</span>
<span>{token.account}</span>
<span>{otp} - {expires}</span>
</div>
</>
)
}
```
Now save and go back to your browser and you should see that our secret keys are now being displayed as tokens:
![](/wp-content/uploads/2024/03/token-display-1.png)That is pretty cool, the only problem is you need to refresh the page to refresh the token. We will take care of that in part 3 of this series as well as handling fetching tokens from our backend.