Last active
September 16, 2018 12:42
-
-
Save Fabianopb/e6939727ad4735bb3027653b68037c99 to your computer and use it in GitHub Desktop.
Authentication for express-react-ts-ci Medium post
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as jwt from "express-jwt"; | |
// A-ha! So this is where the AUTH_SHARED_SECRET from .env is used! | |
export const authorize = jwt({ | |
secret: process.env.AUTH_SHARED_SECRET | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as crypto from "crypto"; | |
import * as jwt from "jsonwebtoken"; | |
import * as mongoose from "mongoose"; | |
// Declare the model interface | |
interface User extends mongoose.Document { | |
email: string; | |
hash: string; | |
salt: string; | |
setPassword(password: string): void; | |
isPasswordValid(password: string): boolean; | |
generateJwt(): { token: string; expiry: Date }; | |
} | |
// Declare the model schema | |
const userSchema = new mongoose.Schema({ | |
email: { | |
type: String, | |
// Important! We want users to be unique | |
unique: true, | |
required: true | |
}, | |
hash: { | |
type: String, | |
required: true | |
}, | |
salt: { | |
type: String, | |
required: true | |
} | |
}); | |
// Define some public methods for our model | |
class UserClass { | |
private _id: string; | |
private email: string; | |
private salt: string; | |
private hash: string; | |
// Create a salt and hash from the password | |
public setPassword(password: string) { | |
this.salt = crypto.randomBytes(16).toString("hex"); | |
this.hash = crypto.pbkdf2Sync(password, this.salt, 100000, 512, "sha512").toString("hex"); | |
} | |
// Check if hashes match | |
public isPasswordValid(password: string): boolean { | |
const hash = crypto.pbkdf2Sync(password, this.salt, 100000, 512, "sha512").toString("hex"); | |
return this.hash === hash; | |
} | |
// Generate access token for 30 minutes | |
public generateJwt(): { token: string; expiry: Date } { | |
const expiry = new Date(); | |
expiry.setMinutes(expiry.getMinutes() + 30); | |
const token = jwt.sign({ | |
_id: this._id, | |
email: this.email, | |
exp: Math.round(expiry.getTime() / 1000), | |
}, process.env.AUTH_SHARED_SECRET); | |
return { token, expiry }; | |
} | |
} | |
// Important! Don't forget to use loadClass so your new methods will be included in the model | |
userSchema.loadClass(UserClass); | |
export default mongoose.model<User>("User", userSchema); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as bodyParser from "body-parser"; | |
import { Router } from "express"; | |
import * as mongoose from "mongoose"; | |
import * as passport from "passport"; | |
import { Strategy } from "passport-local"; | |
import { authorize } from "../config"; | |
import User from "./user.model"; | |
passport.use(new Strategy({ usernameField: "email" }, async (username, password, done) => { | |
try { | |
// Tries to find the user matching the given username | |
const user = await User.findOne({ email: username }); | |
// Check if the password is valid | |
if (user && user.isPasswordValid(password)) { | |
return done(null, user); | |
} else { | |
// Throws an error if credentials are not valid | |
throw new Error("Invalid credentials"); | |
} | |
} catch (error) { | |
return done(error); | |
} | |
})); | |
const router = Router(); | |
router.route("/register").post(bodyParser.json(), async (request, response) => { | |
try { | |
const user = new User(); | |
user.email = request.body.email; | |
// Set the password hash with the method created in the model | |
user.setPassword(request.body.password); | |
await user.save(); | |
// Returns a new token upon registering a user | |
const tokenSignature = user.generateJwt(); | |
return response.status(200).json(tokenSignature); | |
} catch (error) { | |
return response.status(400).send(error); | |
} | |
}); | |
router.route("/login").post(bodyParser.json(), (request, response) => { | |
// Use passport to authenticate user login | |
passport.authenticate("local", (error, user) => { | |
if (!user) { | |
return response.status(400).json({ error: error.message }); | |
} | |
// If login is valid generate a token and return it to the user | |
const tokenSignature = user.generateJwt(); | |
return response.status(200).json(tokenSignature); | |
})(request, response); | |
}); | |
// This is an example of a protected route. Notice that we call `authorize` in the first place! | |
router.route("/profile").get(authorize, async (request, response) => { | |
const user = await User.findById(request.user._id); | |
return response.status(200).json(user); | |
}); | |
export default router; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment