Gathering detailed insights and metrics for @goodrequest/passport-jwt-wrapper
Gathering detailed insights and metrics for @goodrequest/passport-jwt-wrapper
Gathering detailed insights and metrics for @goodrequest/passport-jwt-wrapper
Gathering detailed insights and metrics for @goodrequest/passport-jwt-wrapper
JWT Authentication library developed and used by Goodrequest s.r.o.
npm install @goodrequest/passport-jwt-wrapper
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
276 Commits
3 Watching
10 Branches
18 Contributors
Updated on 21 Dec 2022
TypeScript (99.07%)
JavaScript (0.93%)
Cumulative downloads
Total Downloads
Last day
0%
1
Compared to previous day
Last week
-87.9%
4
Compared to previous week
Last month
100%
42
Compared to previous month
Last year
-78.8%
1,056
Compared to previous year
36
Authentication Library developed and used by the GoodRequest s.r.o. It is based on the express framework for Node.js runtime using JWTs. It is wrapper around passportjs library designed to minimize boilerplate. This library should take of the user authentication, it is divided into modules:
npm i --save @goodrequest/passport-jwt-wrapper
1import { initAuth } from './index' 2import * as passport from 'passport' 3 4initAuth(passport, { 5 userRepository, 6 refreshTokenRepository, 7 invitationTokenRepository?, 8 passwordResetTokenRepository? 9 } 10)
invitationTokenRepository
is used for saving and checking validity of invitation tokens.
This means, that invitations can be cancelled.
passwordResetTokenRepository
is similarly used to save and check the validity of password reset tokens.
This can be used to cancel password reset.
1import { Login, RefreshToken, PasswordReset, Logout, LogoutEverywhere, ApiAuth } from '@slonik923/passport-jwt-wrapper' 2 3import * as postLogin from './post.login' 4import * as postResetPasswordRequest from './post.resetPasswordRequest' 5import schemaMiddleware from '../../../middlewares/schemaMiddleware' 6 7const router = Router() 8 9router.post('/login', 10 schemaMiddleware(postLogin.requestSchema), 11 Login.guard, 12 postLogin.workflow) 13 14router.post('/logout', 15 ApiAuth.guard(), 16 schemaMiddleware(Logout.requestSchema), 17 Logout.workflow) 18 19router.post('/logout-everywhere', 20 ApiAuth.guard(), 21 schemaMiddleware(LogoutEverywhere.requestSchema), 22 LogoutEverywhere.workflow) 23 24router.post('/refresh-token', 25 schemaMiddleware(RefreshToken.requestSchema), 26 RefreshToken.endpoint) 27 28router.post('/reset-password-request', 29 schemaMiddleware(postResetPasswordRequest.requestSchema()), 30 postResetPasswordRequest.workflow) 31 32router.post('/reset-password', 33 schemaMiddleware(PasswordReset.requestSchema), 34 PasswordReset.guard, 35 PasswordReset.workflow)
postlogin.workflow
(and postLogin.requestSchema
): user creation is not in the scope of this librarypostResetPasswordRequest.workflow
(and postResetPasswordRequest.requestSchema
): Reset password email should be
sent from this endpointAuthGuard
is middleware which checks if the request includes valid access_token
based on jwt
extractor specified in the config.
It needs to be used as function call: AuthGuard()
, since it uses passport which is provided after this guard is
imported to your project.
Internally calls userRepository.getUserById
to retrieve the user and when checkAccessToken
is set to
true, refreshTokenRepository.isRefreshTokenValid
is caleed to find out if the access token is valid.
Access token is not only valid, when refresh token issued with given access token was invalidated.
LibConfig
:
{
checkAccessToken: boolean
passport: IPassportConfig
i18next: i18next.InitOptions
}
IPassportConfig
Example:
passport: {
local: {
usernameField: 'email',
passwordField: 'password',
session: false,
},
jwt: {
secretOrKey: process.env.JWT_SECRET,
api: {
exp: '15m',
jwtFromRequest: ExtractJwt.fromExtractors(
[ExtractJwt.fromAuthHeaderAsBearerToken(), ExtractJwt.fromUrlQueryParameter('t')]),
refresh: {
exp: '4h',
}
},
passwordReset: {
exp: '4h',
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
},
invitation: {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
exp: '30d',
}
}
},
i18next.InitOptions
Example:
{
preload: ['en', 'sk'],
fallbackLng: 'en',
ns: ['error', 'translation'],
defaultNS: 'translation',
detection: {
order: ['header']
},
backend: {
loadPath: 'locales/{{lng}}/{{ns}}.json',
jsonIndent: 2
},
nsSeparator: ':',
keySeparator: false,
returnNull: false
}
Library read from config using config package. Config needs to have properties specified in IPassportConfig interface.
ENV variable | Development | Production | Note |
---|---|---|---|
JWT_SECRET | required | required | development/test/production |
workflow
-> runer
and endpoint
-> workflow
. See issue 69joi
package version to v17.7.0getUserByEmail
and getUserById
method. See issue 58This library is divided into modules:
Used for logging in users - exchanging user credential for access and refresh tokens. Exports can be found here.
Refresh tokens have longer validity tha access tokens and can be exchanged for new access and refresh token. Refresh tokens are used just once (refresh token rotation). Exports can be found here.
Module for securing endpoint. Guard is used to verify that request contains valid access token. Exports can be found here.
Endpoint for logging users out. This invalidates whole token family - one user session. Exports can be found here.
Endpoint for logging user out from every device (every session). Exports can be found here.
Module used for managing invitation tokens. User invitation is typically done by sending emails, which is not part of this library, so the endpoint needs to be implemented separately. Exports can be found here.
Module for resetting user passwords. Similarly to invitation, this is done by sending emails, so the endpoint needs to be implemented separately. Exports can be found here.
Each token types has different payload, expiration and audience.
{uid, rid, fid} // userID, refreshTokenID, familyID
{uid, fid, jti} // userID, familyID, JWTID
invitationTokenRepository
is provided to the init
function){ uid } // userID
passwordResetTokenRepository
is provided to the init
function){ uid } // userID
Access and refresh tokens includes familyID
.
This is used to identify login flow.
New familyID
is given every time, user logs in (enter credentials), which means it identifies user session (from login to logout / expiration).
This ID is also passed to every subsequent refresh token.
It is needed for implementing refresh token rotation, but it is also useful for differentiating sessions. When user (attacker) tries to use the same refresh token (RT1) second time, each refresh token issued based on RT1 needs to be invalidated. This means every refresh token from the same token family.
This library should be used in all GR products, so it needs to be highly customizable. Customization is achieved by a combination of technics.
Library needs access to users and user tokens, but the these are stored different for every project, so to archive compatibility repository pattern is used. When the library is initialized it needs repositories for creating / getting / invalidating user tokens (JWTs) and for getting users.
Another way to achieve for greater adaptability is to export helper functions which are used internally on each level. These helper functions can be used to create custom middlewares or endpoints.
Each of the modules exports its parts:
getToken[s]
: Helper function for getting tokens (access, refresh, invitation, password reset).guard
: Passport.js authenticate function. Used as middleware to secure endpoints.strategy
: Passport.js strategy.strategyVerifyFunction
: Helper function used in the strategy.workflow
: Main function for every endpoint. Can be used to write custom endpoint / middleware.endpoint
: Whole express endpoint.requestSchema
: Joi request schema. Should be for schema validation for given endpoint.resposneSchema
: Joi response schema. Can be used for documentation.Express endpoints ((req, res, next)
). They return object, typically JWTs.
Need to be named workflow
, so swagger documentation is properly generated.
Logout.endpoint
: Returns just the message. Internally invalidates refresh token family.LogoutEverywhere.endpoint
: Returns just the message. Internally invalidates all users refresh tokens.PasswordReset.endpoint
: Returns just the message. Changes user password and invalidates all user refresh tokens, if userRepository.invalidateUserRefreshTokens
method is provided.RefreshToken.endpoint
: Returns new access and refresh tokens. Used refresh token is invalidated, since this library is using refresh token rotation.Internal function used by endpoint.
express middlewares (calls next
function):
Login.guard
: helper function calling passport.authenticate
. Should be used before used
specified login endpointResetPassword.guard
: just a helper middleware for the password resetApiAuth.guard
: see Authentication GuardFunction used in project specific middlewares, or endpoints.
Login.getTokens(userID: string | number, familyID?: string | number)
: Used in the login endpoint and in refresh token endpoint.PasswordReset.getToken(email: string)
: Used in the reset password endpoint. Token created by this function should be sent to the user (probably by email).
This function is accessible without authorization, so it should not leak any information about users, that's why the execution should take approximately same time for valid and invalid input. More in the constant time chapterInvitation.getToken(userID: string | number)
: Should be user in the user invitation endpoint. Returns token with given userID.Every module which exports endpoint also exports Joi requestSchema
and responseSchema
router.post('/logout',
ApiAuth.guard(),
schemaMiddleware(Logout.requestSchema),
Logout.endpoint)
This library also exports helper functions, enums and types. All exports can be found in the index.ts.
Some methods can leak information about user based on the execution time. One of these methods is PasswordReset.getToken
.
It is accessible without authorization, so the attacker could find out emails of the registered users, which could be a problem for some applications.
That why execution of this method should be more, less constant.
Execution times before triage:
1average execution time: 0.0087ms 2average invalid execution time: 0.0008ms 3average valid execution time: 0.0166ms
There is huge difference in execution time based on valid input vs. invalid input.
Execution times before triage:
1average execution time: 0.0136ms 2average invalid execution time: 0.0141ms 3average valid execution time: 0.0131ms
Execution now takes more time, when the input is invalid, but that can change when passwordResetToken repository is used, since only valid tokens will be saved.
Measurements heavily depend on the compilation and code optimization done by compiler, so in production this could vary.
These numbers are average from 1000 iterations, after 10 000 iterations warm-up, so JIT compiler can do it's job.
More on these tests can in the getToken.test.ts
.
No vulnerabilities found.
No security vulnerabilities found.