Finper uses Passport.js with two strategies —Documentation Index
Fetch the complete documentation index at: https://mintlify.com/soker90/finper/llms.txt
Use this file to discover all available pages before exploring further.
local (username + bcrypt password verification on login) and jwt (token validation on every protected request). Tokens are short-lived JWTs signed with a server-side secret and silently refreshed on every authenticated response so that an active user never gets logged out mid-session.
Authentication flow
Local Passport strategy verifies credentials
The login controller calls
passport.authenticate('local', cb) manually (not as Express middleware). The LocalStrategy registered in local-strategy-passport-handler.ts lowercases the username, looks up the user with usersRepository.findByUsername, and verifies the password with bcrypt.compareSync(password, userDocument.password). If either check fails, Passport calls done(null, false) and the controller returns HTTP 401.API signs a JWT and responds
On successful authentication the controller calls
usersService.signToken(user.username), which delegates to signToken({ username }) in helpers/sign-token.ts. That helper calls jwt.sign(params, config.jwt.secret, { expiresIn: config.jwt.timeout }) where config.jwt.timeout is '1h'. The response body contains the signed token:Client stores the token
The client saves the token to
localStorage[FINPER_TOKEN] and calls authService.setSession(token), which sets axios.defaults.headers.common.Authorization = \Bearer $`. Every subsequent request leaves the browser with the standard Authorization: Bearer` header:Protected endpoints validate the JWT
Every protected route applies
authMiddleware before the controller. The middleware calls passport.authenticate('jwt', cb). The JwtStrategy registered in jwt-strategy-passport-handler.ts extracts the token from the Authorization: Bearer header using ExtractJwt.fromAuthHeaderAsBearerToken(), verifies the signature against config.jwt.secret, and loads the user via usersRepository.findByUsername(jwtToken.username). On success, authMiddleware sets req.user = user.username (a plain string).Token is refreshed on every authenticated response
After setting
req.user, authMiddleware calls refreshToken(res, username), which signs a new 1-hour token and writes it to the Token response header along with Access-Control-Expose-Headers: *, Token. The client-side axios response interceptor reads this header and updates localStorage[FINPER_TOKEN], keeping the session alive for as long as the user is active.Auth endpoints
| Method | Path | Auth Required | Description |
|---|---|---|---|
POST | /api/auth/login | No | Accepts { username, password }, verifies credentials, and returns { token }. |
POST | /api/auth/register | No (but gated by ALLOW_REGISTRATION) | Creates a new user and returns { token }. Returns 403 if registration is disabled. |
GET | /api/auth/me | JWT | Validates the current token. Returns 204 No Content. The response header carries a refreshed token. |
Token header convention
Finper uses the standard
Authorization: Bearer <token> request header. authService.setSession sets axios.defaults.headers.common.Authorization so every outgoing axios call includes it automatically.The response header that carries the refreshed token is named Token (capital T — HTTP headers are case-insensitive). The API sets Access-Control-Expose-Headers: *, Token so the browser’s CORS policy permits the client to read it. The response interceptor in axios.ts reads response.headers.token and updates both localStorage and the axios default Authorization header, implementing continuous silent token refresh.When calling the API from a script or external tool, send the token as Authorization: Bearer <token>.Registration control
New account creation is disabled by default. ThePOST /api/auth/register endpoint is guarded by registrationEnabledMiddleware, which reads config.allowRegistration:
ALLOW_REGISTRATION is not set or is any value other than the string 'true', the middleware calls next(Boom.forbidden('Registration is disabled').output) and the request never reaches the controller.
To enable registration (e.g. for first-time setup), set the environment variable before starting the API: