Closed sunnixx closed 7 years ago
There is some discussion of this on #153
@sunnixx I am no expert on this but after some trial and error I have managed to authenticate routes by storing JWT inside cookie that way I can even authenticate against my external apis without much trouble I just need to share secret key. I found using sessions much more awkward/complicated.
@kolpav can you share some code / rep ?
@sunnixx There is also this, https://github.com/iaincollins/nextjs-starter
@njj thanks, I've looked into this. I've come across a simpler solution but it's not optimal.
server.get('/', (req, res) => {
if(req.user){
return app.render(req, res, '/index', req.query)
}else{
res.redirect('/login');
}
})
I don't recommend this, but for the time being this does the trick.
@sunnixx Share more of your example if possible, the community will be thankful :)
@njj definitely, I'll share some more examples for the authentication part.
//PASSPORT STRATEGY
passport.use(new Strategy(function(username, password, done){
User.findOne({username:username},function(err,user){
if(err) return done(err);
if(!user){
return done(null,false);
}
if(!user.comparePassword(password)){
return done(null,false);
}
return done(null,user);
});
}));
//passport Serialization
passport.serializeUser(function(user,done){
done(null,user._id);
});
//passport Deserialize
passport.deserializeUser(function(id,done){
User.findById(id,function(err,user){
if(err) return done(err);
done(null,user);
})
});
//MIDDLEWARE
server.use(bodyParser.json())
server.use(bodyParser.urlencoded({extended: false}))
server.use(cookieParser())
server.use(session({
secret: process.env.SESSION_SECRET || secret.key,
resave: true,
saveUninitialized: false,
store: new MongoStore({url:secret.database})
}))
server.use(passport.initialize())
server.use(passport.session())
//Handling Authentication on routes
server.get('/', (req,res) =>{
if(req.user){
app.render(req,res, '/index',req.query);
}else{
res.redirect('/login');
}
})
server.post('/login', passport.authenticate('local',{failureRedirect: '/login'}), (req,res) => {
res.redirect('/');
}
Hope this helps !! 👍
@sunnixx Looks good, I'd even recommend that isUserAuthenticated
(or whatever they named it) middleware method that they do in most of the Passport examples. Just so you can tack it on to your routes w/o having to always add that logic for checking the user.
@sunnixx looks good. Did the solution work? I've seen others solutions( https://nextjs-starter.now.sh and https://github.com/luisrudge/next.js-auth0 ) but as far as I can tell, neither leverage the backend ...?
Also does the server authentication refresh the entire page? I'm afraid any state on the client will be lost ( unless saved somewhere ) in a browser refresh
I'm also having issues with passport local auth strategy - serverside code works as expected, logging in user when correct username and password is supplied, until attempting to render route after successful login. The redirect hangs and doesn't render '/profile' page. After a few seconds I get the following:
GET /profile 200 52.725 ms - - GET /_next/on-demand-entries-ping?page=/login 200 2.336 ms - - GET /_next/on-demand-entries-ping?page=/login 200 4.323 ms - - GET /_next/on-demand-entries-ping?page=/login 200 4.777 ms - - Disposing inactive page(s): /
Example Server Code (cut down for brevity)
// Initialise Passport
server.use(passport.initialize())
server.use(passport.session())
server.get('/login',
(req, res) => {
if (req.user === undefined) {
return app.render(req, res, '/login', req.params)
}
else {
return app.render(req, res, '/profile', req.params);
}
}
);
server.post('/login', (req, res, next) => {
// Provided by connect-ensure-login function
let returnURL = req.session.returnTo
console.log("ReturnURL:", returnURL);
console.log("Req Method:", req.method);
console.log("Req URL:", req.url);
passport.authenticate('local', (err, user, info) => {
if (err) {
// Authentication failed - Error 500 - Server Error
return next(err);
}
if (!user) {
// Authentication failed - Error 401 Missing Credentials
return res.status(401).json(info)
}
req.login(user, (err) => {
if (err) { return next(err); }
// Trigger User Login - not working!
return res.redirect('/profile');
});
})(req, res, next)
});
server.get('/logout', (req, res) => {
req.logout()
res.redirect('/')
// return app.render(req, res, '/')
}
)
Login Component:
import React, { Component } from 'react'
import { Button, Form, Grid, Header, Image, Loader, Message, Segment } from 'semantic-ui-react'
// Services
import Session from '../services/session'
export default class extends Component {
static async getInitialProps({req}) {
// On the sign in page we always force get the latest session data from the
// server by passing 'true' to getSession. This page is the destination
// page after logging or linking/unlinking accounts so avoids any weird
// edge cases.
const session = new Session({req})
const sess = await session.getSession(true)
console.log(sess);
return {session: sess}
}
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: '',
password: '',
csrfToken: this.props.session.csrfToken,
xhrMessage: null,
formError: false,
isLoading: false
}
}
handleChange = (e, { name, value }) => this.setState({ [name]: value })
handleSubmit = (e) => {
// e.preventDefault()
const { email, password } = this.state
let message
let xhr = new XMLHttpRequest()
xhr.open("POST", "/login")
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.onreadystatechange = () => {
if (xhr.readyState < 4) {
this.setState({ isLoading: true });
}
else if (xhr.readyState === 4) {
if (xhr.status === 401) {
let response = JSON.parse(xhr.responseText);
return this.setState({ formError: true, xhrMessage: response.message, isLoading: false });
}
if (xhr.status === 500) {
return this.setState({ formError: true, xhrMessage: "Something went wrong, check your internet connection and try again", isLoading: false });
}
}
}
xhr.onerror = () => {
Error('XMLHttpRequest error: Unable to get confirmation')
}
xhr.send(
JSON.stringify({
email: email,
password: password,
_csrf: this.state.csrfToken
})
)
}
render() {
const { email, password, xhrMessage, formError } = this.state
return (
<div className='login-form'>
<style>
{`
div.login-form {
height: 100%;
}
`}
</style>
<Grid
textAlign='center'
style={{ height: '700px' }}
verticalAlign='middle'
>
<Grid.Column style={{ maxWidth: 450 }}>
<Header as='h2' color='green' textAlign='center'>
Log-in to your account
</Header>
<Form size='large' onSubmit={this.handleSubmit} error={formError}>
<Segment stacked>
<Loader active={this.state.isLoading} />
<Message
error
header="Login Unsuccessful"
content={xhrMessage}
/>
<input type="hidden" name="_csrf" value={this.props.session.csrfToken}/>
<Form.Input
fluid
icon='user'
iconPosition='left'
name='email'
value={email}
placeholder='E-mail address'
onChange={this.handleChange}
/>
<Form.Input
fluid
icon='lock'
iconPosition='left'
placeholder='Password'
type='password'
name='password' value={password}
onChange={this.handleChange}
/>
<Form.Button content='Submit' color='green' fluid size='large'/>
</Segment>
</Form>
<Message>
New to us? <a href='#'>Sign Up</a>
</Message>
</Grid.Column>
</Grid>
</div>
)
}
}
The session object is derived from Iain Collins example - https://nextjs-starter.now.sh. I'm pulling my hair out here, any help appreciated!
I apprecaite this is late, but it in case it helps someone else, all you need to do to add login support is something like this:
express.post(`login`, (req, res) => {
const email = req.body.email || null
const password = req.body.password || null
find({email: email, password: hashPassword(password)})
.then(user => {
if (user === true) {
req.logIn(user, (err) => {
if (err) throw err
return res.redirect('/login?success=true')
})
return res.redirect('/login?success=false')
})
.catch(err => {
return res.redirect('/error')
})
})
If you are using the render function - e.g. return app.render(req, res, '/login', req.params)
- to render pages on sign in routes, you might want to swap them out for redirects - e.g. return res.redirect('/login')
.
Ended up getting it working. Axios post requests weren't, for some reason, allowing the res.redirect to work. So I had to implement a solution which involved sending the route via json to the client side, where I used conditionals to route to the appropriate pages using
window.location = '/index'
Have any of you been able to implement flash messages using express-flash or flash (npm package, not Adobe)? I have no idea how to do it with React and I cannot find any sort of documentation around. Even if you could point me in the direction of someones Github who has implemented it in one of their public projects, that would be great!
Sorry to post on a dead thread... but I just stumbled upon this.
In case anyone is having trouble with implementing passport and NextJS, I have a working example of passport-local 1.0.0
&& next 6.1.1
here: https://github.com/jimmylee/next-postgres. The example is just a boilerplate example so you'll have to do some more work to get it production ready and secure.
Hi Sorry I am new to Next.js, so I am basically stuck at a place where I don't know how to authenticate a route with passport.js Normally we would just do this:
and we can check whether the user is authenticated to access the route. but with passport.js and custom express server, the above strategy doesn't seem to be working.
How can I edit my current code to fit the above description.
Thank You.