Putting Docusaurus behind authentication through Azure Static Web Apps
Then you create a documentation system that you are hosting on Azure Static Web Apps, but suddenly the request comes in to put authentication behind it. How do you get started?
Normally this would be a lengthy task. We need a server to check credentials, implement OAuth, ... BUT Azure made this SUPER-easy by providing handy routes on our Static Web Apps BY DEFAULT:
/.auth/me
check the current logged in user/.auth/login/<provider>
(e.g.,/.auth/login/aad
) login to the provider/.auth/logout
logout
When we login, it will provide something like this:
{
"clientPrincipal": {
"identityProvider": "aad",
"userId": "your_user_id",
"userDetails": "[email protected]",
"userRoles": [
"anonymous",
"authenticated"
]
}
}
which we can then check to see if the user details for example include our domain configured in the settings.
What will we be creating?
In our case, we simply want an authentication wall in front of our project.
Project Structure
First create the following files in your Docusaurus project:
├── docusaurus.config.js
└── src
├── components
| ├── Auth
| | └── index.js
| ├── Loading
| | ├── index.js
| | └── styles.css
├── theme
| └── Root.js
└── constants.js
in the above we are:
- Creating components for requiring a user to be logged in (
Root.js
)- this works together with
Auth
andLoading
as re-usable components
- this works together with
- Defining constants for our project for easy management
Creating our Files
Let's create these files
src/components/Auth/index.js
import React, {useEffect, useState} from 'react';
import {Redirect, useLocation} from '@docusaurus/router';
import Loading from '../Loading';
import {CHECK_DOMAIN, SWA_PATH_LOGIN, SWA_PATH_ME} from '../../constants';
/**
* In Azure Static Web Apps we can get the user from the /.auth/me endpoint.
*
* Example
* {
* "clientPrincipal": {
* "identityProvider": "aad",
* "userId": "f906824d20b542919bcf31287b89a70e",
* "userDetails": "[email protected]",
* "userRoles": [
* "anonymous",
* "authenticated"
* ]
* }
* }
*/
const getUser = async () => {
const response = await fetch(SWA_PATH_ME);
const payload = await response.json();
const {clientPrincipal} = payload;
return clientPrincipal;
};
export function AuthCheck({children}) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
async function loadUser() {
try {
const user = await getUser();
setUser(user);
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
}
loadUser();
}, []);
const location = useLocation();
let from = location.pathname;
if (isLoading) {
return <Loading />;
}
// If we are not logged in, redirect to the login page
if (!user?.userDetails) {
window.location.href = `${SWA_PATH_LOGIN}?post_login_redirect_uri=${from}`;
return <>Authenticating...</>;
}
// If we are logged in but not authorized, show a message
if (
user?.userDetails &&
user?.userDetails?.indexOf(`@${CHECK_DOMAIN}`) === -1
) {
return <div>No access</div>;
}
return children;
}
src/components/Loading/index.js
import React from 'react';
import './styles.css';
export default function Loading() {
return (
<div className="overlay">
<div className="overlayDoor" />
<div className="overlayContent">
<div className="loader">
<div className="inner" />
</div>
</div>
</div>
);
}
src/components/Loading/styles.css
/* src/components/Loading/styles.css */
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 100000000;
}
.overlay .overlayDoor:before,
.overlay .overlayDoor:after {
content: '';
position: absolute;
width: 50%;
height: 100%;
background: #111;
transition: 0.5s cubic-bezier(0.77, 0, 0.18, 1);
transition-delay: 0.8s;
}
.overlay .overlayDoor:before {
left: 0;
}
.overlay .overlayDoor:after {
right: 0;
}
.overlay.loaded .overlayDoor:before {
left: -50%;
}
.overlay.loaded .overlayDoor:after {
right: -50%;
}
.overlay.loaded .overlayContent {
opacity: 0;
margin-top: -15px;
}
.overlay .overlayContent {
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
transition: 0.5s cubic-bezier(0.77, 0, 0.18, 1);
background: #fff;
}
.overlay .overlayContent .skip {
display: block;
width: 130px;
text-align: center;
margin: 50px auto 0;
cursor: pointer;
color: #fff;
font-family: 'Nunito';
font-weight: 700;
padding: 12px 0;
border: 2px solid #fff;
border-radius: 3px;
transition: 0.2s ease;
}
.overlay .overlayContent .skip:hover {
background: #ddd;
color: #444;
border-color: #ddd;
}
.loader {
width: 128px;
height: 128px;
border: 3px solid #222222;
border-bottom: 3px solid transparent;
border-radius: 50%;
position: relative;
animation: spin 1s linear infinite;
display: flex;
justify-content: center;
align-items: center;
}
.loader .inner {
width: 64px;
height: 64px;
border: 3px solid transparent;
border-top: 3px solid #222222;
border-radius: 50%;
animation: spinInner 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes spinInner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(-720deg);
}
}
src/components/theme/Root.js
import React from 'react';
import {AuthCheck} from '../components/Auth';
export default function Root({children}) {
return <AuthCheck children={children} />;
}
src/components/theme/constants.js
export const SWA_PATH_LOGIN = '/.auth/login/aad';
export const SWA_PATH_LOGOUT = '/.auth/logout';
export const SWA_PATH_ME = '/.auth/me';
export const CHECK_DOMAIN = 'example.com';
Conclusion
And that's it! It couldn't be any simpler to put an auth wall in front of your page!
Member discussion