Skip to content
This repository has been archived by the owner on Jul 15, 2020. It is now read-only.

Commit

Permalink
Fixes #10 (Session handling bug)
Browse files Browse the repository at this point in the history
* Session state now updates correctly after sign in on all browsers, including Interent Explorer.

* The most important element of the fix was adding componentDidMount() call to update session data in auth/signin page (as it’s the oauth callback page).

Changes to simply the flow in recent refactoring made this easier to fix.
  • Loading branch information
iaincollins committed Mar 17, 2017
1 parent 1f5ace1 commit 84de080
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 20 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -11,6 +11,8 @@ It includes session support (with CSRF and XSS protection), email based sign-in

All functionality works both client and server side - including without JavaScript support in the browser.

*Important!* Please upgrade to version 2.8 or newer as this resolves all known cross browser compatibility issues, including session behaviour with Internet Explorer.

## Demo

You can try it out at https://nextjs-starter.now.sh
Expand Down
10 changes: 6 additions & 4 deletions components/session.js
@@ -1,4 +1,5 @@
/* global window */
/* global localStorage */
/* global XMLHttpRequest */
/**
* A class to handle signing in and out and caching session data in sessionStore
Expand Down Expand Up @@ -71,6 +72,7 @@ export default class Session {

// If force update is set, clear data from store
if (forceUpdate === true) {
this._session = {}
this._removeLocalStore('session')
}

Expand Down Expand Up @@ -132,7 +134,7 @@ export default class Session {
let xhr = new XMLHttpRequest()
xhr.open('POST', '/auth/email/signin', true)
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
xhr.onreadystatechange = () => {
xhr.onreadystatechange = async () => {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
return reject(Error('XMLHttpRequest error: Error while attempting to signin'))
Expand Down Expand Up @@ -181,22 +183,22 @@ export default class Session {
// We handle that silently by just returning null here.
_getLocalStore(name) {
try {
return JSON.parse(window.localStorage.getItem(name))
return JSON.parse(localStorage.getItem(name))
} catch (err) {
return null
}
}
_saveLocalStore(name, data) {
try {
window.localStorage.setItem(name, JSON.stringify(data))
localStorage.setItem(name, JSON.stringify(data))
return true
} catch (err) {
return false
}
}
_removeLocalStore(name) {
try {
window.localStorage.removeItem(name)
localStorage.removeItem(name)
return true
} catch (err) {
return false
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "nextjs-starter",
"version": "2.7.0",
"version": "2.8.0",
"description": "A starter Next.js project with email and oAuth authentication",
"author": "Iain Collins <me@iaincollins.com>",
"license": "ISC",
Expand Down
42 changes: 28 additions & 14 deletions pages/auth/signin.js
Expand Up @@ -14,17 +14,31 @@ export default class extends Page {
return {session: await session.getSession(true)}
}

async componentDidMount() {
// Get latest session data after rendering on client
// Any page that is specified as the oauth callback should do this
const session = new Session()
this.state = {
email: this.state.email,
session: await session.getSession(true)
}
}

constructor(props) {
super(props)
this.state = {
email: ''
email: '',
session: this.props.session
}
this.handleSubmit = this.handleSubmit.bind(this)
this.handleEmailChange = this.handleEmailChange.bind(this)
}

handleEmailChange(event) {
this.setState({email: event.target.value.trim()})
this.setState({
email: event.target.value.trim(),
session: this.state.session
})
}

async handleSubmit(event) {
Expand All @@ -43,28 +57,28 @@ export default class extends Page {

render() {
let signinForm = <div/>
if (this.props.session.user) {
if (this.state.session.user) {
let linkWithFacebook = <p><a className="button button-oauth button-facebook" href="/auth/oauth/facebook">Link with Facebook</a></p>
if (this.props.session.user.facebook) {
linkWithFacebook = <form action="/auth/oauth/facebook/unlink" method="post"><input name="_csrf" type="hidden" value={this.props.session.csrfToken}/><button className="button button-oauth" type="submit">Unlink from Facebook</button></form>
if (this.state.session.user.facebook) {
linkWithFacebook = <form action="/auth/oauth/facebook/unlink" method="post"><input name="_csrf" type="hidden" value={this.state.session.csrfToken}/><button className="button button-oauth" type="submit">Unlink from Facebook</button></form>
}

let linkWithGoogle = <p><a className="button button-oauth button-google" href="/auth/oauth/google">Link with Google</a></p>
if (this.props.session.user.google) {
linkWithGoogle = <form action="/auth/oauth/google/unlink" method="post"><input name="_csrf" type="hidden" value={this.props.session.csrfToken}/><button className="button button-oauth" type="submit">Unlink from Google</button></form>
if (this.state.session.user.google) {
linkWithGoogle = <form action="/auth/oauth/google/unlink" method="post"><input name="_csrf" type="hidden" value={this.state.session.csrfToken}/><button className="button button-oauth" type="submit">Unlink from Google</button></form>
}

let linkWithTwitter = <p><a className="button button-oauth button-twitter" href="/auth/oauth/twitter">Link with Twitter</a></p>
if (this.props.session.user.twitter) {
linkWithTwitter = <form action="/auth/oauth/twitter/unlink" method="post"><input name="_csrf" type="hidden" value={this.props.session.csrfToken}/><button className="button button-oauth" type="submit">Unlink from Twitter</button></form>
if (this.state.session.user.twitter) {
linkWithTwitter = <form action="/auth/oauth/twitter/unlink" method="post"><input name="_csrf" type="hidden" value={this.state.session.csrfToken}/><button className="button button-oauth" type="submit">Unlink from Twitter</button></form>
}

signinForm = (
<div>
<h3>You are signed in</h3>
<p>Name: <strong>{this.props.session.user.name}</strong></p>
<p>Email address: <strong>{this.props.session.user.email}</strong></p>
<p>Email verified: <strong>{(this.props.session.user.verified) ? 'Yes' : 'No'}</strong></p>
<p>Name: <strong>{this.state.session.user.name}</strong></p>
<p>Email address: <strong>{this.state.session.user.email}</strong></p>
<p>Email verified: <strong>{(this.state.session.user.verified) ? 'Yes' : 'No'}</strong></p>
<p>You can link your account to your other accounts so you can sign in with them too.</p>
{linkWithFacebook}
{linkWithGoogle}
Expand All @@ -84,7 +98,7 @@ export default class extends Page {
signinForm = (
<div>
<form id="signin" method="post" action="/auth/email/signin" onSubmit={this.handleSubmit}>
<input name="_csrf" type="hidden" value={this.props.session.csrfToken}/>
<input name="_csrf" type="hidden" value={this.state.session.csrfToken}/>
<h3>Sign in with email</h3>
<p>
<label htmlFor="email">Email address</label><br/>
Expand All @@ -105,7 +119,7 @@ export default class extends Page {
}

return (
<Layout session={this.props.session}>
<Layout session={this.state.session}>
<h2>Authentication</h2>
{signinForm}
<h3>How it works</h3>
Expand Down
2 changes: 1 addition & 1 deletion routes/passport-strategies.js
Expand Up @@ -31,7 +31,7 @@ exports.configure = ({
passport.deserializeUser(function (id, next) {
User.get(id, function (err, user) {
// Note: We don't return all user profile fields to the client, just ones
// that are whitelisted here to limit the amount of users' data we expose.
// that are whitelisted here to limit the amount of user data we expose.
next(err, {
id: user.id,
name: user.name,
Expand Down

0 comments on commit 84de080

Please sign in to comment.