visit
On Backend Side:
On the Front-end side:
SuperTokens is a versatile authentication and authorization solution for your applications.
node -v
A basic knowledge of HTML, CSS, JavaScript, and React
Take a peek at how we developed our React app and used SuperTokens to authenticate it.
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 . It includes the whole project setup that will get you started.
We'll use the pre-built EmailPassword Recipe to access the , which will look something like this. ⬇
The supertokens website has the documentation for this recipe. Now, fork the
Dependencies used:
"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"
},
The following use cases are demonstrated in this demo app.
npm install
Now it's time to put this demo app to work.
npm run dev
The example app will run on (), while the API server will run on ().
The Session is initialized in the app.js file:
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.
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)} />
)}
</>
);
}
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>
</>
)
}
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>
);
}
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>
);
}
}
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>
</>
)
}
let supertokens = require("supertokens-node");
let Session = require("supertokens-node/recipe/session");
The supertokens node package must first be initialized:
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: "//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.
app.get("/songs", verifySession(), async (req, res) => {
const resp = await axios.get('//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 super tokens make this method available.
SuperTokens takes charge of token creation and session management when the user logs into the demo app.
For the most up-to-date information, follow on Twitter.
Follow me on Twitter at .
If you want to understand more about SuperTokens, I recommend reading last year's blog article. ⬇
First Published