基于OAuth2.0与JWT实现Node.js+Express REST API的特定应用授权
Great question! Since you don't require user login and just need to restrict API access to your own trusted apps (Web, Android, iOS), the OAuth 2.0 Client Credentials Flow (with a special adjustment for web apps) paired with JWT is perfect here. This approach lets your apps authenticate themselves to the API without involving end users, just like Amazon's unauthenticated product browsing.
1. Core Concept: OAuth2 Client Credentials Flow
This OAuth2 flow is designed for application-to-application authentication (not user-to-app). Each of your apps (Web, Android, iOS) gets a unique client_id and client_secret (web apps are an exception—more on that later). The app sends these credentials to your authentication server to get a JWT, then uses that JWT to access protected API endpoints.
2. Step 1: Set Up Trusted Client Credentials
First, create unique credentials for each app and store them securely:
- Assign a unique
client_id(e.g.,web-app-123,android-app-456,ios-app-789) - Generate a strong
client_secretfor each mobile app (web apps can't safely store secrets, so we'll use PKCE instead) - Store these credentials in an encrypted database or environment variables—never hardcode them in client-side code
3. Step 2: Build the Authentication Endpoint (Get JWT)
Create a /oauth/token endpoint that validates client credentials and returns a signed JWT. We'll use the jsonwebtoken library for token generation.
First, install dependencies:
npm install express jsonwebtoken body-parser dotenv
Example Authentication Endpoint Code
const express = require('express'); const jwt = require('jsonwebtoken'); const bodyParser = require('body-parser'); require('dotenv').config(); const app = express(); app.use(bodyParser.json()); // Trusted clients (store in encrypted DB in production) const trustedClients = [ { client_id: 'android-app-456', client_secret: 'android-secret-yyy', app_type: 'android' }, { client_id: 'ios-app-789', client_secret: 'ios-secret-zzz', app_type: 'ios' }, { client_id: 'web-app-123', app_type: 'web' } // Web app doesn't have a secret ]; // Token endpoint for mobile apps app.post('/oauth/token', (req, res) => { const { client_id, client_secret } = req.body; // Validate mobile app credentials const client = trustedClients.find(c => c.client_id === client_id && c.client_secret === client_secret ); if (!client) { return res.status(401).json({ error: 'Invalid client credentials' }); } // Sign JWT with client info and expiration const token = jwt.sign( { client_id: client.client_id, app_type: client.app_type }, process.env.JWT_SECRET, // Store this in .env file { expiresIn: '1h' } // Token expires after 1 hour ); res.json({ access_token: token, token_type: 'Bearer', expires_in: 3600 }); });
4. Step 3: Add JWT Validation Middleware for API Endpoints
Create a middleware function that checks for a valid JWT in the request header and verifies it's from a trusted client.
Example Validation Middleware
// JWT authentication middleware const authenticateClient = (req, res, next) => { const authHeader = req.headers.authorization; // Check if token exists in header if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Authorization token required' }); } const token = authHeader.split(' ')[1]; try { // Verify token signature and decode payload const decoded = jwt.verify(token, process.env.JWT_SECRET); // Ensure the client is still trusted (extra security layer) const client = trustedClients.find(c => c.client_id === decoded.client_id); if (!client) { return res.status(403).json({ error: 'Unauthorized client' }); } // Attach client info to request for use in API endpoints req.client = client; next(); } catch (err) { return res.status(401).json({ error: 'Invalid or expired token' }); } }; // Protected API endpoint example (like product browsing) app.get('/api/products', authenticateClient, (req, res) => { // You can customize responses based on app type if needed res.json({ products: [ { id: 1, name: 'Wireless Headphones', price: 99.99 }, { id: 2, name: 'Portable Charger', price: 29.99 } ], accessed_by: req.client.app_type }); }); // Start server const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });
5. Client-Side Implementation Notes
Mobile Apps (Android/iOS)
- Store the
client_idandclient_secretsecurely:- Android: Use Jetpack Security (EncryptedSharedPreferences) to store secrets
- iOS: Use Keychain Services instead of UserDefaults
- On app startup, send a POST request to
/oauth/tokenwith the credentials to get a JWT - Include the JWT in the
Authorization: Bearer <token>header for all API requests - When the token expires, automatically request a new one
Web Apps
Web apps can't safely store client_secret (it can be extracted from frontend code), so use the OAuth2 Authorization Code Flow with PKCE instead:
- Generate a random
code_verifierand hash it to create acode_challenge - Redirect the user to your authorization endpoint (e.g.,
/oauth/authorize) withclient_id,code_challenge, andresponse_type=code - Your server returns an authorization code
- The frontend sends the code and
code_verifierto/oauth/tokento get a JWT - Use the JWT like mobile apps for API requests
6. Extra Security Best Practices
- Always use HTTPS: Prevent credential and token interception
- Short token expiration: Use 1-hour tokens (adjust as needed) and auto-refresh
- Rotate secrets: Regularly update
client_secretvalues if they're compromised - Restrict token scope: Add scopes to JWTs (e.g.,
scope:products_read) to limit what each app can access - Rate limiting: Add rate limits to
/oauth/tokento prevent brute-force attacks
内容的提问来源于stack exchange,提问作者Pranay Kumar

