-
Notifications
You must be signed in to change notification settings - Fork 445
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
Project Auth API-W16 #334
base: master
Are you sure you want to change the base?
Project Auth API-W16 #334
Changes from all commits
37d566d
61e901c
a45eeda
39bf8e4
100ecae
58893e8
d5f63b1
fa5013e
105f75a
da935fd
7701d93
29c4e13
8da55f2
47e819a
3130ddc
2d16aab
b998a37
c1e1f84
4e18635
8819b39
e8bc6a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,26 @@ | ||
# Project Auth API | ||
|
||
Replace this readme with your own information about your project. | ||
|
||
Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. | ||
Project Auth is a web application designed to provide user authentication functionalities. The application features a frontend built using React.js and a backend developed with Node.js and Express.js. User data is stored securely in a MongoDB database, and password hashing is implemented using bcrypt for enhanced security. | ||
|
||
## The problem | ||
|
||
Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? | ||
Challenges Encountered: | ||
|
||
Backend Setup: Setting up the backend with Node.js and Express.js required careful configuration of routes and middleware for authentication. | ||
|
||
Frontend Integration: Integrating the frontend with the backend API endpoints and managing authentication state in React was challenging. | ||
|
||
User Authentication and Security: Implementing secure authentication and password hashing with bcrypt and JWT authentication required careful implementation. | ||
|
||
Cross-Origin Resource Sharing (CORS): Handling CORS issues during development and configuring CORS middleware in the backend were challenges. | ||
|
||
Technologies Used: | ||
|
||
Frontend: React.js, Axios | ||
Backend: Node.js, Express.js, MongoDB | ||
Authentication: JSON Web Tokens (JWT), bcrypt | ||
|
||
## View it live | ||
|
||
Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about. | ||
BE: https://project-auth-2zcr.onrender.com | ||
FE: https://authentication-bmm.netlify.app/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
node_modules | ||
.env | ||
package-lock.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,73 @@ | ||
import bcrypt from "bcrypt"; | ||
import crypto from "crypto"; | ||
import cors from "cors"; | ||
import dotenv from "dotenv"; | ||
import express from "express"; | ||
import jwt from "jsonwebtoken"; | ||
import mongoose from "mongoose"; | ||
import expressListEndpoints from "express-list-endpoints"; | ||
import asyncHandler from "express-async-handler"; | ||
|
||
const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; | ||
dotenv.config(); | ||
|
||
const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-auth"; | ||
mongoose.connect(mongoUrl); | ||
mongoose.Promise = Promise; | ||
|
||
const { Schema, model } = mongoose; | ||
|
||
const userSchema = new Schema({ | ||
username: { | ||
type: String, | ||
unique: true, | ||
required: true, | ||
minlength: 3, | ||
}, | ||
email: { | ||
type: String, | ||
unique: true, | ||
required: true, | ||
}, | ||
password: { | ||
type: String, | ||
required: true, | ||
required: true, | ||
minlength: 6, | ||
}, | ||
accessToken: { | ||
type: String, | ||
default: () => crypto.randomBytes(16).toString("hex"), | ||
}, | ||
}); | ||
|
||
const User = model("User", userSchema); | ||
|
||
// Function to generate JWT | ||
const generateAccessToken = (userId) => { | ||
return jwt.sign({ userId }, process.env.ACCESS_TOKEN_SECRET, { | ||
expiresIn: "24h", | ||
}); | ||
}; | ||
Comment on lines
+46
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fun that you tried out jwt! |
||
|
||
// Middleware to athenticate the token | ||
const authenticateToken = async (req, res, next) => { | ||
const authHeader = req.headers["authorization"]; | ||
const token = authHeader && authHeader.split(" ")[1]; | ||
if (token == null) return res.sendStatus(401); | ||
|
||
try { | ||
const decoded = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET); | ||
req.user = await User.findById(decoded.userId); | ||
next(); | ||
} catch (err) { | ||
return res.sendStatus(403); | ||
} | ||
}; | ||
|
||
// Defines the port the app will run on. Defaults to 8080, but can be overridden | ||
// when starting the server. Example command to overwrite PORT env variable value: | ||
// PORT=9000 npm start | ||
const port = process.env.PORT || 8080; | ||
const port = process.env.PORT || 9000; | ||
const app = express(); | ||
|
||
// Add middlewares to enable cors and json body parsing | ||
|
@@ -18,10 +76,80 @@ app.use(express.json()); | |
|
||
// Start defining your routes here | ||
app.get("/", (req, res) => { | ||
res.send("Hello Technigo!"); | ||
const endpoints = expressListEndpoints(app); | ||
res.json(endpoints); | ||
}); | ||
|
||
//Registration Endpoint | ||
//http://localhost:9000/register | ||
app.post("/register", async (req, res) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to REST naming conventions, endpoints should be named after what they return or in this case create. So this endpoint could be named /users |
||
try { | ||
const { username, email, password } = req.body; | ||
const hashedPassword = bcrypt.hashSync(password, 10); | ||
const user = new User({ username, email, password: hashedPassword }); | ||
const savedUser = await user.save(); | ||
|
||
// Generate access token for the registered user | ||
const accessToken = generateAccessToken(savedUser._id); | ||
res.status(201).json({ id: savedUser._id, accessToken }); | ||
} catch (err) { | ||
console.error("Error creating user:", err); // Log the actual error for debugging | ||
let errorMessage = "Could not create user"; | ||
if (err.code === 11000) { | ||
// Duplicate key error | ||
if (err.keyPattern && err.keyPattern.username) { | ||
errorMessage = "Username already exists"; | ||
} else if (err.keyPattern && err.keyPattern.email) { | ||
errorMessage = "Email already exists"; | ||
} | ||
} else if (err.errors) { | ||
// Validation errors | ||
const errorFields = Object.keys(err.errors); | ||
if (errorFields.length > 0) { | ||
errorMessage = err.errors[errorFields[0]].message; | ||
} | ||
} | ||
res.status(400).json({ message: errorMessage, errors: err.errors }); | ||
} | ||
}); | ||
|
||
// Sign-in Endpoint | ||
app.post("/login", async (req, res) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And this could be called /sessions |
||
const { email, password } = req.body; | ||
const user = await User.findOne({ email }); | ||
|
||
if (!user || !(await bcrypt.compare(password, user.password))) { | ||
return res.status(401).json({ error: "Invalid credentials" }); | ||
} | ||
|
||
const accessToken = generateAccessToken(user._id); | ||
await User.findByIdAndUpdate(user._id, { accessToken }); | ||
res.json({ accessToken, username: user.username }); | ||
}); | ||
|
||
// Authenticated endpoint | ||
app.get("/secrets", authenticateToken, (req, res) => { | ||
res.json({ secret: "This is secret content" }); | ||
}); | ||
|
||
app.get( | ||
"/logged-in", | ||
authenticateToken, | ||
asyncHandler(async (req, res) => { | ||
res.status(200).json({ | ||
success: true, | ||
response: { | ||
message: "User is logged in", | ||
}, | ||
}); | ||
}) | ||
); | ||
|
||
/* const secret = crypto.randomBytes(64).toString("hex"); | ||
console.log(secret); */ | ||
|
||
// Start the server | ||
app.listen(port, () => { | ||
console.log(`Server running on http://localhost:${port}`); | ||
}); | ||
console.log("Contents of process.env:", process.env); |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[build] | ||
base = "frontend/" | ||
publish = "dist" | ||
command = "npm run build" | ||
|
||
[[redirects]] | ||
from = "/*" | ||
to = "/index.html" | ||
status = 200 |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,36 @@ | ||
import AuthenticatedContent from "./components/AuthenticatedContent"; | ||
import RegistrationForm from "./components/RegistrationForm"; | ||
import SignInForm from "./components/SignInForm"; | ||
import { useState } from 'react' | ||
|
||
|
||
export const App = () => { | ||
return <div>Find me in src/app.jsx!</div>; | ||
const [isAuthenticated, setIsAuthenticated] = useState(false) | ||
const [username, setUsername] = useState('') | ||
|
||
const handleSigninSuccess = (username) => { | ||
setIsAuthenticated(true) | ||
setUsername(username) | ||
} | ||
|
||
const handleSignOut = () => { | ||
setIsAuthenticated(false) | ||
setUsername('') | ||
sessionStorage.removeItem('accessToken') | ||
} | ||
|
||
return ( | ||
<div className="app-container"> | ||
{!isAuthenticated ? ( | ||
<div> | ||
<h1>Authentication System</h1> | ||
<RegistrationForm /> | ||
<h2>Sign in</h2> | ||
<SignInForm onSignInSuccess={handleSigninSuccess} /> | ||
</div> | ||
) : ( | ||
<AuthenticatedContent username={username} onSignOut={handleSignOut} /> | ||
)} | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
const AuthenticatedContent = ({ username, onSignOut }) => { | ||
return ( | ||
<div className="authenticated-content"> | ||
<h2>🎉 Congratulations, {username}! 🎉</h2> | ||
<p>You've successfully signed in!</p> | ||
<p>Your profile is currently under construction, but stay tuned for more exciting features coming your way!</p> | ||
<p>Meanwhile, sit back and relax😉.</p> | ||
<button onClick={onSignOut} className="button">Sign Out</button> | ||
</div> | ||
); | ||
}; | ||
|
||
export default AuthenticatedContent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think one of these is enough?