Skip to main content

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.

Finper uses Passport.js with two strategies — 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

1

Client sends login request

The React client posts credentials to the API:
POST /api/auth/login
Content-Type: application/json

{ "username": "alice", "password": "s3cr3t" }
2

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.
3

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:
{ "token": "<signed-jwt>" }
4

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:
GET /api/accounts
Authorization: Bearer <signed-jwt>
5

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).
6

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

MethodPathAuth RequiredDescription
POST/api/auth/loginNoAccepts { username, password }, verifies credentials, and returns { token }.
POST/api/auth/registerNo (but gated by ALLOW_REGISTRATION)Creates a new user and returns { token }. Returns 403 if registration is disabled.
GET/api/auth/meJWTValidates 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. The POST /api/auth/register endpoint is guarded by registrationEnabledMiddleware, which reads config.allowRegistration:
// packages/api/src/config/index.ts
allowRegistration: process.env.ALLOW_REGISTRATION === 'true',
If 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:
ALLOW_REGISTRATION=true make start-api
Disable it again once your account is created to prevent unauthorized sign-ups.

Example: login with curl

# 1. Obtain a token
curl -s -X POST http://localhost:3008/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "alice", "password": "s3cr3t"}' | jq -r '.token'

# Store it in a shell variable
TOKEN=$(curl -s -X POST http://localhost:3008/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "alice", "password": "s3cr3t"}' | jq -r '.token')

Example: authenticated request

# 2. Use the token on subsequent requests via the Authorization header
curl -s http://localhost:3008/api/accounts \
  -H "Authorization: Bearer $TOKEN" | jq

Build docs developers (and LLMs) love