Answer the question
In order to leave comments, you need to log in
Authorization. What is the best way to organize it?
Actually, I would like to know how to correctly and safely organize authorization for your application in 2021. I want offline authorization (without authorization through google or facebook, etc.).
Answer the question
In order to leave comments, you need to log in
Let's assume for definiteness that there is a stack of Express.js (server architecture) + Passport.js (library for authorization) + Mongoose.js (ODM for Mongo database) + React.js (client). It is worth noting that if offline authorization is needed and OAuth is definitely not required in the future, then Passport can not be used. In this case, everything is quite simple to implement on your own. Passport is necessary (in my opinion) mainly for the unification of all authorization methods.
Now let's decide on the degree and method of protection. The most popular solution among tokens is JWT tokens. The point of using them is that
passport.use('byUsernameAndPassword', // я так назвал свою стратегию
new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
// passReqToCallback: true,
session: false,
}, (username, password, done) => {
User
.verifyByUsernameAndPassword(username, password) // примечание: это, конечно, не базовый метод, он написан "вручную"
.then(user => done(null, user))
.catch(error => done(error, false, error.message));
})
);
passport.use(
new JwtStrategy({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),// passport сам дешифрует токен и проверит его валидность, хотя это и не сложно сделать вручную, если решите passport не использовать
secretOrKey: process.env.ACCESS_SECRET,
session: false,
}, ({ username }, done) => {
// на самом деле можно и не доставать пользователя из базы, просто расшифровать jwt, как я уже писал.
// Доставать из базы можно только тогда, когда его нужно поменять (скажем, пользователю нужно добавить заказ в корзину)
// меньше запросов к БД - быстрее работает
User
.findOne({ phoneNumber: username })
.then(user => {
if (!user) return done(null, false, 'This user has been deleted or changed. Please, login again.');
return done(null, user);
})
.catch(err => done(err));
})
);
module.exports = passport;
const auth = require('../controller/auth');
const router = express.Router();
router.post('/login', passport.authenticate('byUsernameAndPassword'), auth.logIn);
const sessionRouter = express.Router();
// из Refresh-токена извлекаем собственно строку
function decodeRefreshStringFromToken(refreshToken, errorCallback) {
const decoded = require('jsonwebtoken').verify(refreshToken, process.env.REFRESH_SECRET, (error, decoded) => {
return error ? error : decoded;
});
if (decoded instanceof Error) // обработка ошибок
return decoded.refreshToken;
}
// для любой работы с сессией в теле запроса будем ожидать Refresh-токена
sessionRouter.use((req, res, next) => {
if (!req.body.refreshToken) return next(new RefreshTokenUnauthorizedError());
res.locals.refreshString = decodeRefreshStringFromToken(req.body.refreshToken, next);
return next();
});
// выход - удаление сессии из БД
sessionRouter.post('/logout', auth.logOut); // todo: jwtAuthMiddleware here&&
// сессия становится неактивной (пользователь закрыл приложение) - зачем нужен данный роут? В БД у сессии будем хранить поле isActive. Если пользователь сидит сразу с двух устройств - подозрительно)
sessionRouter.post('/interrupt', auth.interruptSession);
// обновляем сессию, когда время жизни Refresh-токена вышло
sessionRouter.post('/refresh', auth.refreshSession);
router.use('/session', sessionRouter);
module.exports = router;
module.exports.logIn = async (req, res, next) => {
req.user.installSession(req.useragent)
.then(newSessionData => {
res.json(newSessionData);
next();
});
};
userSchema.methods.installSession = async function(useragent) {
const newSession = this.createSession(useragent); // метод реализован ниже
if (this.sessions.length > 5) this.sessions = []; // допустим ограничение максимум в 5 сессий
if (this.sessions.find(session => session.isActive)) {// выходить из всех активных сессий, может даже удалять их}
this.sessions.push(newSession);
await this.save();
return this.makeSessionData(newSession.refreshToken);
};
userSchema.methods.createSession = function({ browser, os, platform }) {
return {
refreshString: this.generateRefreshString(),
browser,
os,
platform,
};
};
userSchema.methods.makeSessionData = function(refreshString) {
const {
id,
name,
phoneNumber,
role,
} = this;
const refreshToken = this.makeRefreshJwtToken(refreshString);
return ({ id, name, phoneNumber, role, refreshToken, accessToken: this.makeAccessJwtToken({ username: this.username }), tokenType: 'Bearer', expiresIn: process.env.ACCESS_EXPIRES_IN, });
};
userSchema.methods.makeRefreshJwtToken = function(refreshString) {
return jwt.sign(
{ refreshString },
REFRESH_SECRET,
{ expiresIn: REFRESH_EXPIRES_IN });
};
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question