Skip to content

Commit 1442b70

Browse files
committed
feat: add simple auth example
This is a demo of client-only routes that require a user to be authenticated before viewing. It does NOT show how to build secure authentication. (This is called out in the README.) Signed-off-by: Jason Lengstorf <[email protected]>
1 parent d169097 commit 1442b70

File tree

24 files changed

+562
-0
lines changed

24 files changed

+562
-0
lines changed

examples/simple-auth/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Gatsby Authentication Demo
2+
3+
This is a simplified demo to show how an authentication workflow is implemented in Gatsby.
4+
5+
The short version is:
6+
7+
* Gatsby statically renders all unauthenticated routes as usual
8+
* Authenticated routes are whitelisted as client-only
9+
* Logged out users are redirected to the login page if they attempt to visit private routes
10+
* Logged in users will see their private content
11+
12+
## A Note About Security
13+
14+
This example is less about creating an example of secure, production-ready authentication, and more about showing Gatsby's ability to support dynamic content in client-only routes.
15+
16+
For production-ready authentication solutions, take a look at [Auth0](https://auth0.com) or [Passport.js](http://www.passportjs.org/). Rolling a custom auth system is hard and likely to have security holes. Auth0 and Passport.js are both battle tested and widely used.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
siteMetadata: {
3+
title: 'Gatsby Demo Simple Authentication',
4+
},
5+
plugins: ['gatsby-plugin-react-helmet'],
6+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Implement Gatsby's Node APIs in this file.
3+
*
4+
* See: https://www.gatsbyjs.org/docs/node-apis/
5+
*/
6+
7+
exports.onCreatePage = async ({ page, boundActionCreators }) => {
8+
const { createPage } = boundActionCreators;
9+
10+
// page.matchPath is a special key that's used for matching pages
11+
// only on the client.
12+
if (page.path.match(/^\/app/)) {
13+
page.matchPath = '/app/:path';
14+
15+
// Update the page.
16+
createPage(page);
17+
}
18+
};

examples/simple-auth/package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "gatsby-demo-simple-auth",
3+
"description": "Gatsby demo of simplified authentication flow.",
4+
"version": "1.0.0",
5+
"author": "Jason Lengstorf <[email protected]>",
6+
"dependencies": {
7+
"gatsby": "latest",
8+
"gatsby-link": "latest",
9+
"gatsby-plugin-react-helmet": "latest",
10+
"prop-types": "^15.6.1",
11+
"react-helmet": "^5.2.0",
12+
"react-router-dom": "^4.2.2"
13+
},
14+
"keywords": ["gatsby"],
15+
"license": "MIT",
16+
"scripts": {
17+
"build": "gatsby build",
18+
"develop": "gatsby develop",
19+
"format": "prettier --write 'src/**/*.js'",
20+
"test": "echo \"Error: no test specified\" && exit 1"
21+
},
22+
"devDependencies": {
23+
"prettier": "^1.11.1"
24+
}
25+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from "react"
2+
import View from "./View"
3+
import { getCurrentUser } from "../utils/auth"
4+
5+
const Details = () => {
6+
const { name, legalName, email } = getCurrentUser()
7+
8+
return (
9+
<View title="Your Details">
10+
<ul>
11+
<li>Preferred name: {name}</li>
12+
<li>Legal name: {legalName}</li>
13+
<li>Email address: {email}</li>
14+
</ul>
15+
</View>
16+
)
17+
}
18+
19+
export default Details
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
.form {
2+
margin: 1rem 0;
3+
}
4+
5+
.form__label {
6+
display: block;
7+
font-size: 67.5%;
8+
letter-spacing: 0.125em;
9+
text-transform: uppercase;
10+
}
11+
12+
.form__label + .form__label {
13+
margin-top: 0.5rem;
14+
}
15+
16+
.form__input {
17+
display: block;
18+
font-size: 1rem;
19+
padding: 0.25rem;
20+
}
21+
22+
.form__button {
23+
background-color: rebeccapurple;
24+
border: 0;
25+
color: white;
26+
font-size: 1.25rem;
27+
font-weight: bold;
28+
margin-top: 0.5rem;
29+
padding: 0.25rem 1rem;
30+
transition: background-color 150ms linear;
31+
}
32+
33+
.form__button:hover {
34+
cursor: pointer;
35+
}
36+
37+
.form__button:hover,
38+
.form__button:active,
39+
.form__button:focus {
40+
background-color: color(rebeccapurple lightness(-20%));
41+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from "react"
2+
import { withRouter } from "react-router-dom"
3+
import styles from "./form.module.css"
4+
5+
export default withRouter(({ handleSubmit, handleUpdate, history }) => (
6+
<form
7+
className={styles.form}
8+
method="post"
9+
onSubmit={event => {
10+
handleSubmit(event)
11+
history.push(`/app/profile`)
12+
}}
13+
>
14+
<p className={styles[`form__instructions`]}>
15+
For this demo, please log in with the username <code>gatsby</code> and the
16+
password <code>demo</code>.
17+
</p>
18+
<label className={styles[`form__label`]}>
19+
Username
20+
<input
21+
className={styles[`form__input`]}
22+
type="text"
23+
name="username"
24+
onChange={handleUpdate}
25+
/>
26+
</label>
27+
<label className={styles[`form__label`]}>
28+
Password
29+
<input
30+
className={styles[`form__input`]}
31+
type="password"
32+
name="password"
33+
onChange={handleUpdate}
34+
/>
35+
</label>
36+
<input className={styles[`form__button`]} type="submit" value="Log In" />
37+
</form>
38+
))
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
.header {
2+
background-color: rebeccapurple;
3+
}
4+
5+
.header__wrap {
6+
align-items: baseline;
7+
display: grid;
8+
grid-template-columns: repeat(2, 1fr);
9+
margin: 0 auto;
10+
max-width: 640px;
11+
padding: 1rem 0;
12+
}
13+
14+
.header__heading {
15+
margin: 0;
16+
font-size: 2rem;
17+
}
18+
19+
.header__nav {
20+
font-size: 1.25rem;
21+
margin-top: 0;
22+
text-align: right;
23+
}
24+
25+
.header__link {
26+
color: white;
27+
font-weight: bold;
28+
margin-left: 0.75rem;
29+
margin-top: 0;
30+
padding: 0.25rem;
31+
text-decoration: none;
32+
}
33+
34+
.header__link--home {
35+
font-size: 2rem;
36+
margin-left: -0.25rem;
37+
}
38+
39+
.header__link:hover,
40+
.header__link:active,
41+
.header__link:focus {
42+
background: white;
43+
color: rebeccapurple;
44+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from "react"
2+
import Link from "gatsby-link"
3+
import styles from "./header.module.css"
4+
5+
const Header = () => (
6+
<header className={styles.header}>
7+
<div className={styles[`header__wrap`]}>
8+
<h1 className={styles[`header__heading`]}>
9+
<Link
10+
to="/"
11+
className={`${styles[`header__link`]} ${
12+
styles[`header__link--home`]
13+
}`}
14+
>
15+
Gatsby Profiles
16+
</Link>
17+
</h1>
18+
<nav role="main" className={styles[`header__nav`]}>
19+
<Link to="/" className={styles[`header__link`]}>
20+
Home
21+
</Link>
22+
<Link to="/app/profile" className={styles[`header__link`]}>
23+
Profile
24+
</Link>
25+
<Link to="/app/details" className={styles[`header__link`]}>
26+
Details
27+
</Link>
28+
</nav>
29+
</div>
30+
</header>
31+
)
32+
33+
export default Header
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from "react"
2+
import View from "./View"
3+
import { getCurrentUser } from "../utils/auth"
4+
5+
const Home = () => {
6+
const { name } = getCurrentUser()
7+
8+
return (
9+
<View title="Your Profile">
10+
<p>Welcome back, {name}!</p>
11+
</View>
12+
)
13+
}
14+
15+
export default Home

0 commit comments

Comments
 (0)