sso 인증 기능 구현
This commit is contained in:
@@ -1,51 +1,120 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const jwksClient = require('jwks-rsa');
|
||||
const User = require('../models/user');
|
||||
const axios = require('axios');
|
||||
|
||||
// Cache for JWKS clients to avoid re-fetching the configuration on every request.
|
||||
const jwksClients = {};
|
||||
|
||||
// Function to get the JWKS URI from the OIDC discovery endpoint.
|
||||
async function getJwksUri(issuer) {
|
||||
try {
|
||||
const discoveryUrl = `${issuer}/.well-known/openid-configuration`;
|
||||
console.log(`Fetching OIDC discovery document from: ${discoveryUrl}`);
|
||||
const response = await axios.get(discoveryUrl);
|
||||
console.log(`Successfully fetched OIDC discovery document. JWKS URI: ${response.data.jwks_uri}`);
|
||||
return response.data.jwks_uri;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch OIDC discovery document:', error.message);
|
||||
throw new Error('Could not fetch JWKS URI.');
|
||||
}
|
||||
}
|
||||
|
||||
function fetchKey(client, header, callback) {
|
||||
console.log(`Fetching signing key for kid: ${header.kid}`);
|
||||
client.getSigningKey(header.kid, (err, key) => {
|
||||
if (err) {
|
||||
console.error('Error getting signing key:', err);
|
||||
return callback(err);
|
||||
}
|
||||
const signingKey = key.publicKey || key.rsaPublicKey;
|
||||
console.log('Successfully fetched signing key.');
|
||||
callback(null, signingKey);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function ssoHandler(req, res, next) {
|
||||
// 1. Check if the token is in the query parameters
|
||||
console.log('ssoHandler started.');
|
||||
const { token } = req.query;
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
// 2. Decode the JWT to get the payload
|
||||
// In a real app, you MUST verify the token signature using jwt.verify()
|
||||
// For this demo, we'll just decode to inspect the payload.
|
||||
const decoded = jwt.decode(token);
|
||||
console.log('Token found in query. Verifying...');
|
||||
const untrustedDecoded = jwt.decode(token, { complete: true });
|
||||
|
||||
if (!decoded || !decoded.sub) {
|
||||
return res.status(400).send('Invalid token: "sub" claim is missing.');
|
||||
}
|
||||
|
||||
// 3. Find user by 'sub' claim
|
||||
let user = await User.findBySsoSub(decoded.sub);
|
||||
|
||||
// 4. If user doesn't exist, create a new one (auto-registration)
|
||||
if (!user) {
|
||||
user = await User.createUser({ sso_sub: decoded.sub });
|
||||
}
|
||||
|
||||
// 5. Save user information in the session
|
||||
req.session.userId = user.id;
|
||||
|
||||
// 6. Redirect to the same URL without the token parameter
|
||||
const redirectUrl = req.path;
|
||||
return res.redirect(redirectUrl);
|
||||
|
||||
} catch (error) {
|
||||
console.error('SSO handling failed:', error);
|
||||
return res.status(500).send('An error occurred during SSO processing.');
|
||||
if (!untrustedDecoded) {
|
||||
return res.status(400).send('Invalid token.');
|
||||
}
|
||||
}
|
||||
|
||||
// Attach user to request if session exists
|
||||
if (req.session.userId) {
|
||||
res.locals.user = await User.findById(req.session.userId);
|
||||
const issuer = untrustedDecoded.payload.iss;
|
||||
let client = jwksClients[issuer];
|
||||
|
||||
if (!client) {
|
||||
try {
|
||||
console.log(`Creating new JWKS client for issuer: ${issuer}`);
|
||||
const jwksUri = await getJwksUri(issuer);
|
||||
client = jwksClient({
|
||||
jwksUri: jwksUri,
|
||||
cache: true,
|
||||
rateLimit: true,
|
||||
});
|
||||
jwksClients[issuer] = client;
|
||||
} catch (err) {
|
||||
console.error('Could not create JWKS client.', err);
|
||||
return res.status(500).send('An error occurred during SSO processing due to a configuration error.');
|
||||
}
|
||||
} else {
|
||||
console.log(`Using existing JWKS client for issuer: ${issuer}`);
|
||||
}
|
||||
|
||||
jwt.verify(token, (header, callback) => fetchKey(client, header, callback), { algorithms: ['RS256'] }, async (err, decoded) => {
|
||||
if (err) {
|
||||
console.error('JWT verification failed:', err);
|
||||
return res.status(500).send('An error occurred during SSO processing due to a verification failure.');
|
||||
}
|
||||
|
||||
console.log('JWT verified successfully. Decoded token:', decoded);
|
||||
|
||||
try {
|
||||
if (!decoded || !decoded.sub) {
|
||||
console.error('Invalid token: "sub" claim is missing.');
|
||||
return res.status(400).send('Invalid token: "sub" claim is missing.');
|
||||
}
|
||||
|
||||
console.log(`Looking for user with sso_sub: ${decoded.sub}`);
|
||||
let user = await User.findBySsoSub(decoded.sub);
|
||||
|
||||
if (!user) {
|
||||
console.log('User not found. Creating a new user.');
|
||||
user = await User.createUser({ sso_sub: decoded.sub });
|
||||
console.log('New user created:', user);
|
||||
} else {
|
||||
console.log('User found:', user);
|
||||
}
|
||||
|
||||
req.session.userId = user.id;
|
||||
console.log(`User ID ${user.id} stored in session.`);
|
||||
|
||||
const redirectUrl = req.path;
|
||||
console.log(`Redirecting to ${redirectUrl}`);
|
||||
return res.redirect(redirectUrl);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error during user lookup or creation:', error);
|
||||
return res.status(500).send('An error occurred during user processing.');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.locals.user = null;
|
||||
console.log('No token in query. Proceeding to next middleware.');
|
||||
if (req.session.userId) {
|
||||
console.log(`Session found for user ID: ${req.session.userId}`);
|
||||
res.locals.user = await User.findById(req.session.userId);
|
||||
} else {
|
||||
console.log('No session found.');
|
||||
res.locals.user = null;
|
||||
}
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
module.exports = ssoHandler;
|
||||
|
||||
Reference in New Issue
Block a user