Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add simple auth example #4923

Merged
merged 1 commit into from
Apr 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions examples/simple-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Gatsby Authentication Demo

This is a simplified demo to show how an authentication workflow is implemented in Gatsby.

The short version is:

* Gatsby statically renders all unauthenticated routes as usual
* Authenticated routes are whitelisted as client-only
* Logged out users are redirected to the login page if they attempt to visit private routes
* Logged in users will see their private content

## A Note About Security

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.

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.
6 changes: 6 additions & 0 deletions examples/simple-auth/gatsby-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
siteMetadata: {
title: 'Gatsby Demo Simple Authentication',
},
plugins: ['gatsby-plugin-react-helmet'],
};
18 changes: 18 additions & 0 deletions examples/simple-auth/gatsby-node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/

exports.onCreatePage = async ({ page, boundActionCreators }) => {
const { createPage } = boundActionCreators;

// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/app/)) {
page.matchPath = '/app/:path';

// Update the page.
createPage(page);
}
};
25 changes: 25 additions & 0 deletions examples/simple-auth/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "gatsby-demo-simple-auth",
"description": "Gatsby demo of simplified authentication flow.",
"version": "1.0.0",
"author": "Jason Lengstorf <[email protected]>",
"dependencies": {
"gatsby": "latest",
"gatsby-link": "latest",
"gatsby-plugin-react-helmet": "latest",
"prop-types": "^15.6.1",
"react-helmet": "^5.2.0",
"react-router-dom": "^4.2.2"
},
"keywords": ["gatsby"],
"license": "MIT",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"format": "prettier --write 'src/**/*.js'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"prettier": "^1.11.1"
}
}
19 changes: 19 additions & 0 deletions examples/simple-auth/src/components/Details.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react"
import View from "./View"
import { getCurrentUser } from "../utils/auth"

const Details = () => {
const { name, legalName, email } = getCurrentUser()

return (
<View title="Your Details">
<ul>
<li>Preferred name: {name}</li>
<li>Legal name: {legalName}</li>
<li>Email address: {email}</li>
</ul>
</View>
)
}

export default Details
41 changes: 41 additions & 0 deletions examples/simple-auth/src/components/Form/form.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.form {
margin: 1rem 0;
}

.form__label {
display: block;
font-size: 67.5%;
letter-spacing: 0.125em;
text-transform: uppercase;
}

.form__label + .form__label {
margin-top: 0.5rem;
}

.form__input {
display: block;
font-size: 1rem;
padding: 0.25rem;
}

.form__button {
background-color: rebeccapurple;
border: 0;
color: white;
font-size: 1.25rem;
font-weight: bold;
margin-top: 0.5rem;
padding: 0.25rem 1rem;
transition: background-color 150ms linear;
}

.form__button:hover {
cursor: pointer;
}

.form__button:hover,
.form__button:active,
.form__button:focus {
background-color: color(rebeccapurple lightness(-20%));
}
38 changes: 38 additions & 0 deletions examples/simple-auth/src/components/Form/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react"
import { withRouter } from "react-router-dom"
import styles from "./form.module.css"

export default withRouter(({ handleSubmit, handleUpdate, history }) => (
<form
className={styles.form}
method="post"
onSubmit={event => {
handleSubmit(event)
history.push(`/app/profile`)
}}
>
<p className={styles[`form__instructions`]}>
For this demo, please log in with the username <code>gatsby</code> and the
password <code>demo</code>.
</p>
<label className={styles[`form__label`]}>
Username
<input
className={styles[`form__input`]}
type="text"
name="username"
onChange={handleUpdate}
/>
</label>
<label className={styles[`form__label`]}>
Password
<input
className={styles[`form__input`]}
type="password"
name="password"
onChange={handleUpdate}
/>
</label>
<input className={styles[`form__button`]} type="submit" value="Log In" />
</form>
))
44 changes: 44 additions & 0 deletions examples/simple-auth/src/components/Header/header.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.header {
background-color: rebeccapurple;
}

.header__wrap {
align-items: baseline;
display: grid;
grid-template-columns: repeat(2, 1fr);
margin: 0 auto;
max-width: 640px;
padding: 1rem 0;
}

.header__heading {
margin: 0;
font-size: 2rem;
}

.header__nav {
font-size: 1.25rem;
margin-top: 0;
text-align: right;
}

.header__link {
color: white;
font-weight: bold;
margin-left: 0.75rem;
margin-top: 0;
padding: 0.25rem;
text-decoration: none;
}

.header__link--home {
font-size: 2rem;
margin-left: -0.25rem;
}

.header__link:hover,
.header__link:active,
.header__link:focus {
background: white;
color: rebeccapurple;
}
33 changes: 33 additions & 0 deletions examples/simple-auth/src/components/Header/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react"
import Link from "gatsby-link"
import styles from "./header.module.css"

const Header = () => (
<header className={styles.header}>
<div className={styles[`header__wrap`]}>
<h1 className={styles[`header__heading`]}>
<Link
to="/"
className={`${styles[`header__link`]} ${
styles[`header__link--home`]
}`}
>
Gatsby Profiles
</Link>
</h1>
<nav role="main" className={styles[`header__nav`]}>
<Link to="/" className={styles[`header__link`]}>
Home
</Link>
<Link to="/app/profile" className={styles[`header__link`]}>
Profile
</Link>
<Link to="/app/details" className={styles[`header__link`]}>
Details
</Link>
</nav>
</div>
</header>
)

export default Header
15 changes: 15 additions & 0 deletions examples/simple-auth/src/components/Home.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react"
import View from "./View"
import { getCurrentUser } from "../utils/auth"

const Home = () => {
const { name } = getCurrentUser()

return (
<View title="Your Profile">
<p>Welcome back, {name}!</p>
</View>
)
}

export default Home
40 changes: 40 additions & 0 deletions examples/simple-auth/src/components/Login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react"
import { Redirect } from "react-router-dom"
import Form from "./Form"
import View from "./View"
import { handleLogin, isLoggedIn } from "../utils/auth"

class Login extends React.Component {
state = {
username: ``,
password: ``,
}

handleUpdate(event) {
this.setState({
[event.target.name]: event.target.value,
})
}

handleSubmit(event) {
event.preventDefault()
handleLogin(this.state)
}

render() {
if (isLoggedIn()) {
return <Redirect to={{ pathname: `/app/profile` }} />
}

return (
<View title="Log In">
<Form
handleUpdate={e => this.handleUpdate(e)}
handleSubmit={e => this.handleSubmit(e)}
/>
</View>
)
}
}

export default Login
25 changes: 25 additions & 0 deletions examples/simple-auth/src/components/PrivateRoute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react"
import PropTypes from "prop-types"
import { Redirect, Route } from "react-router-dom"
import { isLoggedIn } from "../utils/auth"

// More info at https://reacttraining.com/react-router/web/example/auth-workflow
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
!isLoggedIn() ? (
// If we’re not logged in, redirect to the home page.
<Redirect to={{ pathname: `/app/login` }} />
) : (
<Component {...props} />
)
}
/>
)

PrivateRoute.propTypes = {
component: PropTypes.any.isRequired,
}

export default PrivateRoute
35 changes: 35 additions & 0 deletions examples/simple-auth/src/components/Status/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react"
import { Link, withRouter } from "react-router-dom"
import { getCurrentUser, isLoggedIn, logout } from "../../utils/auth"
import styles from "./status.module.css"

export default withRouter(({ history }) => {
let details
if (!isLoggedIn()) {
details = (
<p className={styles[`status__text`]}>
To get the full app experience, you’ll need to{` `}
<Link to="/app/login">log in</Link>.
</p>
)
} else {
const { name, email } = getCurrentUser()

details = (
<p className={styles[`status__text`]}>
Logged in as {name} ({email})!{` `}
<a
href="/"
onClick={event => {
event.preventDefault()
logout(() => history.push(`/app/login`))
}}
>
log out
</a>
</p>
)
}

return <div className={styles.status}>{details}</div>
})
11 changes: 11 additions & 0 deletions examples/simple-auth/src/components/Status/status.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.status {
background: lightgrey;
font-size: 87.5%;
padding: 0.25rem;
}

.status__text {
margin: 0 auto;
max-width: 640px;
text-align: right;
}
16 changes: 16 additions & 0 deletions examples/simple-auth/src/components/View/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react"
import PropTypes from "prop-types"
import styles from "./view.module.css"

const View = ({ title, children }) => (
<section className={styles.view}>
<h1 className={styles[`view__heading`]}>{title}</h1>
{children}
</section>
)

View.propTypes = {
title: PropTypes.string.isRequired,
}

export default View
Loading