--- author: mikeconrad categories: - Software Engineering date: "2024-01-17T12:11:00Z" enclosure: - | https:///wp-content/uploads/2024/03/otp-countdown.mp4 29730 video/mp4 tags: - Authentication - Blog Post - React - TypeScript title: Roll your own authenticator app with KeystoneJS and React - pt 3 --- In our previous post we got to the point of displaying an OTP in our card component. Now it is time to refactor a bit and implement a countdown functionality to see when this token will expire. For now we will go ahead and add this logic into our Card component. In order to figure out how to build this countdown timer we first need to understand how the TOTP counter is calculated. In other words, we know that at TOTP token is derived from a secret key and the current time. If we dig into the spec some we can find that time is a reference to Linux epoch time or the number of seconds that have elapsed since January 1st 1970. For a little more clarification check out this Stackexchange [article](https://crypto.stackexchange.com/questions/72558/what-time-is-used-in-a-totp-counter). So if we know that the time is based on epoch time, we also need to know that most TOTP tokens have a validity period of either 30 seconds or 60 seconds. 30 seconds is the most common standard so we will use that for our implementation. If we put all that together then basically all we need is 2 variables: 1. Number of seconds since epoch 2. How many seconds until this token expires The first one is easy: ```javascript let secondsSinceEpoch; secondsSinceEpoch = Math.ceil(Date.now() / 1000) - 1; # This gives us a time like so: 1710338609 ``` For the second one we will need to do a little math but it’s pretty straightforward. We need to divide `secondsSinceEpoch` by 30 seconds and then subtract this number from 30. Here is what that looks like: ```javascript let secondsSinceEpoch; let secondsRemaining; const period = 30; secondsSinceEpoch = Math.ceil(Date.now() / 1000) - 1; secondsRemaining = period - (secondsSinceEpoch % period); ``` Now let’s put all of that together into a function that we can test out to make sure we are getting the results we expect. ```javascript const timer = setInterval(() => { countdown() }, 1000) function countdown() { let secondsSinceEpoch; let secondsRemaining; const period = 30; secondsSinceEpoch = Math.ceil(Date.now() / 1000) - 1; secondsRemaining = period - (secondsSinceEpoch % period); console.log(secondsSinceEpoch, secondsRemaining) if (secondsRemaining == 1) { console.log("timer done") clearInterval(timer) } } ``` Running this function should give you output similar to the following. In this example we are stopping the timer once it hits 1 second just to show that everything is working as we expect. In our application we will want this time to keep going forever: ```javascript 1710339348, 12 1710339349, 11 1710339350, 10 1710339351, 9 1710339352, 8 1710339353, 7 1710339354, 6 1710339355, 5 1710339356, 4 1710339357, 3 1710339358, 2 1710339359, 1 "timer done" ``` Here is a JSfiddle that shows it in action: https://jsfiddle.net/561vg3k7/ We can go ahead and add this function to our Card component and get it wired up. I am going to skip ahead a bit and add a progress bar to our card that is synced with our countdown timer and changes colors as it drops below 10 seconds. For now we will be using a `setInterval` function to accomplish this. Here is what my updated `src/Components/Card.tsx` looks like: ```typescript import { useState } from "react"; import { IToken } from "../App" import { TOTP } from 'totp-generator'; function Card({ token }: { token: IToken }) { const { otp } = TOTP.generate(token.token); const [timerStyle, setTimerStyle] = useState(""); const [timerWidth, setTimerWidth] = useState(""); function countdown() { let secondsSinceEpoch: number; let secondsRemaining: number = 30; const period = 30; secondsSinceEpoch = Math.ceil(Date.now() / 1000) - 1; secondsRemaining = period - (secondsSinceEpoch % period); setTimerWidth(`${100 - (100 / 30 * (30 - secondsRemaining))}%`) setTimerStyle(secondsRemaining < 10 ? "salmon" : "lightgreen") } setInterval(() => { countdown(); }, 250); return ( <>