如何在Node中使用OAuth 2实现认证?求注册登录示例(熟悉JWT)
Hey there! I totally get why OAuth 2.0 can feel confusing when you're already comfortable with JWT—they work together but serve different purposes. Let's walk through a complete Node.js example that covers user registration, OAuth 2.0 login, and protected routes, tying it all to JWT since you know that well.
First: Let's Bridge OAuth 2.0 and JWT
Quick clarity to cut through confusion:
- OAuth 2.0 is an authorization framework (it defines how clients get permission to access resources). It doesn't handle authentication directly, but we can pair it with JWT to manage user identity.
- JWT acts as the
access_tokenin our OAuth flow—once a user authenticates, our server signs a JWT that carries user data (like ID, email) so resource servers can validate it without hitting a database every time.
Step 1: Project Setup & Dependencies
Start by initializing your project and installing the tools we'll need:
npm init -y npm install express jsonwebtoken bcryptjs passport passport-oauth2-client-password passport-http-bearer dotenv
express: Our web frameworkjsonwebtoken: To sign/verify JWT tokensbcryptjs: To hash user passwords securelypassport: Middleware for handling authentication flowspassport-oauth2-client-password: Validates OAuth 2.0 client credentialspassport-http-bearer: Validates JWT bearer tokens for protected routesdotenv: Manages environment variables (like secret keys)
Step 2: User Registration Flow
First, we need a way for users to create accounts. We'll use an in-memory "database" for simplicity—replace this with MongoDB/PostgreSQL in production.
Create a server.js file and add this code:
require('dotenv').config(); const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const passport = require('passport'); const { Strategy: ClientPasswordStrategy } = require('passport-oauth2-client-password'); const { Strategy: BearerStrategy } = require('passport-http-bearer'); const app = express(); app.use(express.json()); // In-memory storage (replace with real DB in production) const users = []; // Pre-registered OAuth client (for app-to-server authentication) const oauthClients = [ { id: 'my-app-client', secret: 'super-secret-client-key', redirectUris: ['http://localhost:3000/callback'] } ]; // 1. Passport setup: Validate OAuth client credentials passport.use(new ClientPasswordStrategy( (clientId, clientSecret, done) => { const client = oauthClients.find(c => c.id === clientId); if (!client || client.secret !== clientSecret) { return done(null, false); } return done(null, client); } )); // 2. Passport setup: Validate JWT bearer tokens passport.use(new BearerStrategy( (token, done) => { try { const decoded = jwt.verify(token, process.env.JWT_SECRET); const user = users.find(u => u.id === decoded.userId); if (!user) return done(null, false); return done(null, user, { scope: '*' }); } catch (err) { return done(null, false); } } )); app.use(passport.initialize()); // User Registration Endpoint app.post('/register', async (req, res) => { const { email, password } = req.body; // Check if user already exists const existingUser = users.find(u => u.email === email); if (existingUser) { return res.status(400).json({ message: 'User with this email already exists' }); } // Hash password before storing const hashedPassword = await bcrypt.hash(password, 10); // Create new user const newUser = { id: Date.now().toString(), email, password: hashedPassword }; users.push(newUser); res.status(201).json({ message: 'User registered successfully', userId: newUser.id }); });
Step 3: OAuth 2.0 Login (Password Grant Flow)
Since we're building our own auth system, the Password Grant is perfect here—it lets users log in with their email/password to get an access token. Add this token endpoint to server.js:
// OAuth 2.0 Token Endpoint (Password Grant) app.post('/oauth/token', passport.authenticate('oauth2-client-password', { session: false }), async (req, res) => { const { username, password, grant_type } = req.body; // Validate grant type if (grant_type !== 'password') { return res.status(400).json({ error: 'unsupported_grant_type' }); } // Find user by email (we're using email as username here) const user = users.find(u => u.email === username); if (!user) { return res.status(401).json({ error: 'invalid_grant' }); } // Verify password const isPasswordValid = await bcrypt.compare(password, user.password); if (!isPasswordValid) { return res.status(401).json({ error: 'invalid_grant' }); } // Sign JWT access token (expires in 1 hour) const accessToken = jwt.sign( { userId: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '1h' } ); // Sign refresh token (for getting new access tokens without re-logging in) const refreshToken = jwt.sign( { userId: user.id }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d' } ); res.json({ access_token: accessToken, token_type: 'Bearer', expires_in: 3600, refresh_token: refreshToken }); });
Step 4: Protect API Routes with OAuth 2.0
Now let's add a protected route that requires a valid JWT access token:
// Protected API Endpoint (only accessible with valid Bearer token) app.get('/api/profile', passport.authenticate('bearer', { session: false }), (req, res) => { res.json({ message: 'This is a protected route!', user: { id: req.user.id, email: req.user.email } }); }); // Refresh Token Endpoint (get new access token without re-logging in) app.post('/oauth/refresh-token', (req, res) => { const { refresh_token } = req.body; try { const decoded = jwt.verify(refresh_token, process.env.JWT_REFRESH_SECRET); const user = users.find(u => u.id === decoded.userId); if (!user) return res.status(401).json({ error: 'invalid_refresh_token' }); const newAccessToken = jwt.sign( { userId: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '1h' } ); res.json({ access_token: newAccessToken, token_type: 'Bearer', expires_in: 3600 }); } catch (err) { res.status(401).json({ error: 'invalid_refresh_token' }); } }); // Start server const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
Step 5: Configure Environment Variables
Create a .env file in your project root:
JWT_SECRET=your-strong-jwt-secret-key-here JWT_REFRESH_SECRET=your-strong-refresh-secret-key-here PORT=5000
How to Test the Flow
Use curl or Postman to test each step:
- Register a user:
curl -X POST http://localhost:5000/register -H "Content-Type: application/json" -d '{"email":"test@example.com","password":"test123"}' - Get an access token:
curl -X POST http://localhost:5000/oauth/token -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=password&username=test@example.com&password=test123&client_id=my-app-client&client_secret=super-secret-client-key" - Access the protected route:
curl http://localhost:5000/api/profile -H "Authorization: Bearer YOUR_ACCESS_TOKEN" - Refresh your access token:
curl -X POST http://localhost:5000/oauth/refresh-token -H "Content-Type: application/json" -d '{"refresh_token":"YOUR_REFRESH_TOKEN"}'
Production Best Practices
- Replace the in-memory storage with a real database (MongoDB, PostgreSQL, etc.).
- Always use HTTPS to prevent token interception.
- Store client secrets and JWT keys in a secure vault (never hardcode them).
- Add rate limiting to login/register endpoints to prevent brute-force attacks.
- For complex use cases, consider using a dedicated OAuth 2.0 library like
oauth2-serverinstead of building from scratch.
内容的提问来源于stack exchange,提问作者Rahul Saini




