DZone
Web Dev Zone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Web Dev Zone > Add Secure HarperDB APIs Using SuperTokens in React

Add Secure HarperDB APIs Using SuperTokens in React

This tutorial will teach us how to make a music player app that uses SuperTokens for authentication and HarperDB for the backend.

Ankur Tyagi user avatar by
Ankur Tyagi
·
Mar. 02, 22 · Web Dev Zone · Tutorial
Like (2)
Save
Tweet
5.54K Views

Join the DZone community and get the full member experience.

Join For Free

Adding authorization and authentication to an app is a common task for developers. When you're launching a new product, a startup, or a side project, it can feel like a mission. This tutorial will teach us how to make a music player app that uses SuperTokens for authentication and HarperDB for the backend.

Some of the App's Most Essential Features 

On Backend Side:
1. Secure your API with almost significantly less and zero configurations.
2. Secure your third-party API on the server side using SuperTokens.
3. Session management is simple.

On the Front-end side:
1. For login, use SuperTokens' library component, which is available as an npm package.
2. There's no need to develop any extra code for authentication.
3. There's no need to maintain tokens on the UI side.
4. You won't have to worry about managing token refresh calls on the UI side because SuperTokens will take care of it for you.

What You'll Discover 

  • How to add authentication using SuperTokens.
  • How to secure 3rd party (HarperDB) endpoints.

Let's Talk About SuperTokens 

  • SuperTokens is an open-source authentication.
  • They help you launch quickly so that you can focus on your core product offering.
  • SuperTokens is 100% open source.
  • Developers can own and manage their user's data.
  • SuperTokens can be run on your premise for free and also has a generously hosted tier for those who don't want to manage it themselves.

SuperTokens is a versatile authentication and authorization solution for your applications.

Prerequisites 

This tutorial assumes the reader has the following:

  • Node installed on their local development machine.
  • You can verify if you do by running the command below in your terminal.

    node -v

    If otherwise, download from here.

Basic knowledge of HTML, CSS, JavaScript, and React. 

A Summary of the Project's Setup 

Yay! This section will create a React music player app and incorporate SuperTokens and HarperDB.

Alternatively, you can utilize the start directory as your project root by cloning the GitHub repository. It includes the whole project setup that will get you started.

How to Begin Using SuperTokens 

SuperTokens provides a variety of ready-to-use recipes from which to choose.

signup or login screen

second signup or login screen


We'll use the pre-built EmailPassword Recipe to access the demo app, which will look something like this.
sign in for react demo app

The SuperTokens website has the documentation for this recipe.

GitHub repository.

Let’s Have a Look at All of the Project Dependencies That Were Used To Make This App


Dependencies used:

JavaScript
 
"dependencies": {
"axios": "^0.21.0",
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"express": "^4.17.1",
"helmet": "^4.6.0",
"morgan": "^1.10.0",
"mysql": "^2.18.1",
"npm-run-all": "^4.1.5",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.0",
"supertokens-auth-react": "^0.17.0",
"supertokens-node": "^8.0.0",
"web-vitals": "^0.2.4"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.6.2",
"nodemon": "^2.0.12"
},


Using the SuperTokens npm Package in a React Application 

The following use cases are demonstrated in this demo app:

  • Login.
  • Sign up.
  • Logout.
  • Forgot password flow.
  • Session management & Calling APIs.

    You can now install the project dependencies with npm:

    npm install

    Now it's time to put this demo app to work.

    npm run dev

The example app will run on (http://localhost:3000), while the API server will run on (http://localhost:3001).

On the Front-end, Authentication 

We use the supertokens-auth-react package on the front end to handle authentication (sending and receiving tokens).

The session is initialized in the app.js file: 

JavaScript
 

SuperTokens.init({
appInfo: {
appName: "Music Player", // TODO: Your app name
apiDomain: getApiDomain(), // TODO: Change to your app's API domain
websiteDomain: getWebsiteDomain(), // TODO: Change to your app's website domain
},
recipeList: [
EmailPassword.init({
emailVerificationFeature: {
mode: "REQUIRED",
},
}),
Session.init(),
],
});


To deliver the token to the server in an API request, we must include the following line.

Session.addAxiosInterceptors(axios);

The Supertokens-auth-react package will handle storing tokens on the client, transmitting tokens to the server, and updating tokens before they expire.

Let’s Discuss the React Component That We Used To Create the Music Player App

In Visual Studio Code, the folder structure appears like this:
visual code studio nested code

1. Music Container (MusicContainer.jsx) 

We manage all of the state-related stuff in this component and render all of the child components. Here, we call the songs endpoint exposed by API server.js to get the list of songs.

JavaScript
 
import React, { useCallback, useState } from "react";
import SongList from "./SongList.jsx/SongList";
import Player from "./Player/Player";
import SongDetail from "./SongDetails/SongDetails";
import axios from "axios";
import { getApiDomain } from "../utils/utils";
import useAsync from "../Hooks/useAsync";

export default function MusicContainer() {
const asyncCallback = useCallback(() => {
return axios.get(`${getApiDomain()}/songs`);
}, []);
const { data } = useAsync(asyncCallback);
const songs = data || [];
const [selectedSong, setSelectedSong] = useState(0);

return (
<>
<SongDetail selectedSongId={selectedSong} songs={songs} />
<SongList selectedSong={selectedSong} songs={songs} selectedSongId={(id) => setSelectedSong(id)} />
{songs.length > 0 && (
<Player songs={songs} selectedSongId={selectedSong} selectSongById={(id) => setSelectedSong(id)} />
)}
</>
);
}


2. Song List (SongList.jsx) 

All of the songs are rendered in this component. And the player component will play each of the songs in the panel when you click on them in the panel.

JavaScript
 
import React from 'react'
import './SongList.css'
import logo from '../../playing.gif'

export default function SongList({songs, selectedSongId, selectedSong}) {
return (
<>
<div className="header">
<div className="track-number">#</div>
<div className="track-title">Title</div>
<div className="track-author">Author</div>
</div>

<div className="song-main">
{' '}
{songs.map((item, index) => (
<div
key={index}
className={`song-list ${index === selectedSong ? 'active' : ''}`}
onClick={() => selectedSongId(index)}
>
{index !== selectedSong ? (
<div className="track-number">{index + 1}</div>
) : (
<div className="index">
<img alt="" src={logo} id="focused" className="small-icon" />
</div>
)}
<div className="track-title">{item.name}</div>
<div className="track-author">{item.author}</div>
</div>
))}
</div>
</>
)
}


3. Player (Player.jsx) 

The HTML5 audio element is used in this player component to play all of the songs in the app.


JavaScript
 
import "./Player.css";
import { useCallback, useEffect, useRef, useState } from "react";
import { forwardsSvg, backwardsSvg, shuffleSvg } from "../svg";
import Progress from "../ProgressBar/ProgressBar";
import SongTime from "./SongTime";

export default function Player({ selectedSongId, songs, selectSongById }) {
const [shuffled, setShuffled] = useState(false);
const [currentTime, setCurrenTime] = useState(0);
const [duration, setDuration] = useState(0);
const [currentVolume, setCurrentVolume] = useState(100);
const [playerState, setPlayerState] = useState(false);
const audioRef = useRef();
let intervalRef = useRef();
let clicked = useRef(false);

const spaceDownFunc = useCallback((event) => {
if (event.keyCode === 32 && !clicked.current) {
clicked.current = true;
document.getElementsByClassName("main-control")[0].click();
}
}, []);
const spaceUpFunc = useCallback((event) => {
if (event.keyCode === 32 && clicked.current) {
clicked.current = false;
}
}, []);

useEffect(() => {
document.addEventListener("keydown", spaceDownFunc);
document.addEventListener("keyup", spaceUpFunc);
return () => {
clearInterval(intervalRef.current);
document.removeEventListener("keydown", spaceDownFunc);
document.removeEventListener("keyup", spaceUpFunc);
};
}, [spaceDownFunc, spaceUpFunc]);

if (selectedSongId < 0 || selectedSongId > songs.length - 1) {
selectSongById(0);
}

useEffect(() => {
if (audioRef.current) {
audioRef.current.volume = currentVolume / 500;
}
}, [currentVolume]);

const onMusicPlay = (e) => {
e.preventDefault();
setPlayerState((prev) => !prev);
};

const onBackwardClick = () => {
if (selectedSongId > 0) {
selectSongById(selectedSongId - 1);
}
};
const onForwardClick = () => {
if (selectedSongId < songs.length - 1) {
selectSongById(selectedSongId + 1);
}
};

useEffect(() => {
setPlayerState(true);
}, [selectedSongId]);

useEffect(() => {
if (playerState) {
audioRef.current.play();
} else {
audioRef.current.pause();
}
}, [playerState, selectedSongId]);

return (
<div id="player">
<SongTime currentLocation={currentTime} duration={duration} />
<div
className="control"
id={shuffled ? `active` : null}
onClick={() => {
setShuffled(!shuffled);
}}>
{shuffleSvg}
</div>
<div className="control" onClick={onBackwardClick}>
{backwardsSvg}
</div>
<div className="main-control control" onClick={onMusicPlay}>
<i className={`fas fa-${playerState ? "pause" : "play"}-circle`}></i>
</div>
<div className="control" onClick={onForwardClick}>
{forwardsSvg}
</div>
<Progress value={currentVolume} setVolume={(vol) => setCurrentVolume(vol)} />

<audio
id="main-track"
controls
src={songs[selectedSongId].url}
preload="true"
onEnded={() => {
selectSongById(shuffled ? Math.round(Math.random() * songs.length) : selectedSongId + 1);
}}
onLoadedMetadata={() => {
setDuration(audioRef.current.duration);
intervalRef.current = setInterval(() => {
if (audioRef.current) {
setCurrenTime(audioRef.current.currentTime);
} else {
clearInterval(intervalRef.current);
}
}, 1000);
}}
ref={audioRef}
hidden>
Your browser does not support the
<code>audio</code> element.
</audio>
</div>
);
}


4. Progress (Progress.jsx) 

The progress bar component is used to show the song's progress.


JavaScript
 
import React from "react";
import "./ProgressBar.css";

export default class ProgressBar extends React.Component {
constructor(props) {
super(props);
this.state = { showTooltip: false };
}
render() {
return (
<div className="progress">
<input
type="range"
min="0"
max="100"
className="slider"
value={this.props.volume}
onChange={(e) => this.props.setVolume(e.target.value)}
onMouseEnter={() => this.setState({ showTooltip: true })}
onMouseLeave={() => this.setState({ showTooltip: false })}
/>
{this.state.showTooltip && <span className="tooltip">{this.props.volume}</span>}
</div>
);
}
}


5. Song Detail (SongDetail.jsx) 

The track title and album thumbnail image are displayed in this component.


JavaScript
 
import React from 'react'
import './SongList.css'
import logo from '../../playing.gif'

export default function SongList({songs, selectedSongId, selectedSong}) {
return (
<>
<div className="header">
<div className="track-number">#</div>
<div className="track-title">Title</div>
<div className="track-author">Author</div>
</div>

<div className="song-main">
{' '}
{songs.map((item, index) => (
<div
key={index}
className={`song-list ${index === selectedSong ? 'active' : ''}`}
onClick={() => selectedSongId(index)}
>
{index !== selectedSong ? (
<div className="track-number">{index + 1}</div>
) : (
<div className="index">
<img alt="" src={logo} id="focused" className="small-icon" />
</div>
)}
<div className="track-title">{item.name}</div>
<div className="track-author">{item.author}</div>
</div>
))}
</div>
</>
)
}


Securing 3rd Party APIs 

We're using an API server to use the SuperTokens backend package for token management and session management.

screenshot - music player app

let supertokens = require("supertokens-node"); 

let Session = require("supertokens-node/recipe/session");

The supertokens node package must first be initialized:

JavaScript
 
supertokens.init({
framework: "express",
supertokens: {
// TODO: This is a core hosted for demo purposes. You can use this, but make sure to change it to your core instance URI eventually.
connectionURI: "https://try.supertokens.io",
apiKey: "<REQUIRED FOR MANAGED SERVICE, ELSE YOU CAN REMOVE THIS FIELD>",
},
appInfo: {
appName: "SuperTokens Demo App", // TODO: Your app name
apiDomain, // TODO: Change to your app's API domain
websiteDomain, // TODO: Change to your app's website domain
},
recipeList: [EmailPassword.init(
{
override: {
apis: (originalImplementation) => {
return {
...originalImplementation,

signInPOST: async ({formFields, options}) => {
let email = formFields.filter((f) => f.id === "email")[0].value;
let password = formFields.filter((f) => f.id === "password")[0].value;


// const res = await query(`select * from user where email='${email}'`)
if(userId[email]) {
let sessionHandles = await Session.getAllSessionHandlesForUser(userId[email]);
if(sessionHandles.length > 0) {
return {
status: 'SESSION_ALREADY_EXISTS'
}
}
}
let response = await options.recipeImplementation.signIn({ email, password });
if (response.status === "WRONG_CREDENTIALS_ERROR") {
return response;
}
let user = response.user;
userId[email] = user.id;

await Session.createNewSession(options.res, user.id, {}, {});
// query(`insert into user (email, status) values ('${email}', 'ACTIVE')`)
return {
status: "OK",
user,
};
},

}
},
}
}
), Session.init(),

],
});



We exposed the Song endpoint to the react app to retrieve the music list. We're calling the HarperDB endpoint in this endpoint to receive a list of songs from DB.

JavaScript
 

app.get("/songs", verifySession(), async (req, res) => {
const resp = await axios.get('https://functions-custom-tyagi.harperdbcloud.com/ToDoApi/songs');
res.send(resp.data);
});


As a result, the get method's second parameter, verifySession, does the validation(token, Session) for us. The SuperTokens make this method available.

SuperTokens takes charge of token creation and session management when the user logs into the demo app.

Today, I hope you learned something new, and if you did, please like and share it so that others can see it as well.

React (JavaScript library) app security JavaScript authentication

Published at DZone with permission of Ankur Tyagi. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • What Are Microservices?
  • Is NoOps the End of DevOps?
  • 12 Modern CSS Techniques For Older CSS Problems
  • Automation Testing vs. Manual Testing: What's the Difference?

Comments

Web Dev Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo