@supersave/auth
v2.0.3
Published
A library that enables developers to easily build authenticated endpoints for a supersave powered API/backend.
Downloads
11
Readme
@supersave/auth
This package enables you to build a registration / login system on top of supersave. It will also leverage the hooks in supersave to only query the records, in a protected collection, of the querying user. Making sure the user can only see their own information when querying supersave collections.
You can use the auth-client to easily query the API this package exposes.
Authentication methods
- A user can register an account with an emailaddress and password, and login using the created credentials
- Logging into the application can be performed using a magic login, which is a short-lived token that allows the application to retrieve a refresh- and accessToken. Normally this is send out using an email with a link to immediately login.
How does it work?
When you want to protect a supersave collection and store user-specific information in it, you should not invoke addCollection
directly
on your supersave
instance. Instead, use the addCollection
method of this package. This will add an additional column/index
to the collection, userId
. Any queries to the HTTP API that supersave exposes must include a Bearer
token that identifies
a user. And only the records for that specific user will be returned.
Once you have obtained a valid accessToken
for a user, it can be used as the token in an Authorization
header:
Authorization: Bearer xxx
If the accessToken is not valid, a 401 HTTP response code will be returned.
Usage
npm i @supersave/auth
Configuration
First, make sure you have a configured instance of supersave
available. Then, create a new auth
instance using this db.
const { router, middleware, addCollection, stop } = await superSaveAuth(superSave, {
tokenSecret: 'xxx',
accessTokenExpiration: 300,
methods: [{
type: 'local-password',
requestResetPassword: (user, identifier, expires) => { console.log('Send out the reset password token.' )}
}]
});
The four returned parameters can then be used to respectively:
- Register the router for the
auth
endpoints - Middleware that you can use to protect your own custom routes. It will check if the Bearer token is provided, and will place a
userId
variable inresponse.locals
of the express Response variable of your handler. Currently onlyauthenticate
is supported. - The function to use to add secured collections.
- Stop the internal clean up process and stop the related timer interval. The interval is automatically started on initialization, and keeps running until the process stops, or this method is invoked.
// Initialization of the routes
app.use('/api/v1/auth/', router);
app.use(
'/api/v1/secure/collections',
middleware.authenticate,
await superSave.getRouter('/api/v1/secure/collections')
);
// Register secure extensions
await addCollection(entities.Environment);
The configurations options are:
| Name | Required | Type | Default | Description |
| ---------------------- | -------- | ------------------------ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| tokenSecret | Yes | string | | The secret that is used to sign the JWT. It should not be guessable and is not to be read by other processes. |
| tokenAlgorithm | No | string | HS512 | See njwt for a list of supported JWT signing algorithms. |
| accessTokenExpiration | No | number (seconds) | 300 | How many seconds it takes for the JWT token to expire, requiring the fetching of a new accessToken using the refreshToken. |
| refreshTokenExpiration | No | number (seconds) | 7776000 (3 months) | How many seconds it takes for the refresh token to expire, forcing the user to login again. |
| notSecuredEndpoints | No | RegExp[] | [] | One or more RegExps that, if matched again the path, will indicate that that specific path should not be secured. |
| securedEndpoints | No | RegExp[] | [] | The opposite of notSecuredEndpoints
, any paths that match will require a valid Bearer token. |
| hooks | No | object | {} | Functions that are invoked when a specific trigger takes place within this package. See below for more information. |
| methods | Yes | AuthMethod[] | | This is a list of configured authentication methods that are enabled. See AuthMethods below. At least 1 is required to be present. |
| rateLimit | No | false | RateLimitConfig | | See the section on rate limiting below. |
If both securedEndpoints
and notSecuredEndpoints
are set, all paths are treated as secure endpoints.
AuthMethods
Local Password
This authentication method provides a traditional login method using email address and password, and all related functionality (register account, reset password, change password).
| Name | Required | Type | Default | Description |
| ---------------------------- | -------- | ---------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type | No | 'local-password' | | This value must be set to local-password to indicate this is the configuration for the local password auth method. |
| resetPasswordTokenExpiration | No | number | 3600 | The number of seconds after which a reset password token expires. |
| requestResetPassword | Yes | callback | | This callback is invoked when a reset password token has been requested. The application is then expected to send out an email to the user linking to the page to reset the password. Its function signature is requestResetPassword: (user: User, identifier: string, expires: Date) => Promise<void> \| void;
|
Magic Login
The magic login method allows a user to login only using their emailaddress. A token will be generated that has to be emailed to them. That token can then be used to login to the application. An account is created automatically for them, no password is required to be remembered.
| Name | Required | Type | Default | Description |
| -------------------- | -------- | ------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type | No | 'magic-login' | | This value must be set to magic-login to indicate this is the configuration for the magic login auth method. |
| sendMagicIdentifier | yes | callback | | The callback that the application must register to send out the token that will allow the user to login to the application. Its signature is sendMagicIdentifier: (user: User, identifier: string, expires: Date) => Promise<void> \| void;
|
| magicLoginExpiration | no | number | 1800 | The number of seconds after which the magic login token expires and can no longer be used. |
Hooks
The hooks are invoked after their specific trigger is executed. They are all optional. An example use case is to send out e-mails for a trigger, for example when the password changes or a password reset is requested.
| Trigger | Signature | | -------------------- | ---------------------------------------------------------------------- | | registration | (user: User) => void | Promise | | login | (user: User) => void | Promise | | refresh | (user: User) => void | Promise | | changePassword | (user: User) => void | Promise | | requestResetPassword | (user: User, identifier: string) => void | Promise | | doResetPassword | (user: User => void | Promise | | requestMagicLogin | (user: T, identifier: string, expires: Date) => void | Promise; | | magicLogin | (user: User => void | Promise |
Rate Limits
Within this package there are two types of rate limits defined: general rate limits, and rate limits for a specific identifier.
The default rate limits limit the general rates based on IP address, and the identifier rate limit on identifier, email and token attributes.
The default rate limits are:
export const DEFAULT_LIMIT_GENERAL: RateLimit = {
windowMs: 30 * 1000,
max: 5,
};
export const DEFAULT_LIMIT_IDENTIFIER: RateLimit = {
windowMs: 60 * 1000,
max: 5,
keyGenerator(req) {
return req.body?.identifier ?? req.body?.email ?? req.body?.token;
},
};
Which translates for the general rate limit to allow 5 requests in a window of 30 seconds. For the general limit this is 5 requests per 60 seconds, when the key translates to the same value.
You are free to replace one or both of the rate limits with your own configuration.
These rate limits are only applied to the @supersave/auth
endpoints.
HTTP Endpoints
Once the router returned at initialization has been registered at express, the auth endpoints can be invoked.
Depending on the auth methods you have configured, different endpoints are available. The refresh endpoint is always available, regardless of the method used.
These endpoints are implemented in a simplified API in the official auth-client.
Refresh
The refresh endpoint is used to exchange the refreshToken for a new accessToken. Its location is /<prefix>/post
, and must be invoked using POST
.
// Request
export type RefreshRequest = {
token: string;
};
// Response
export type RefreshResponse = RefreshResponseSuccess | RefreshResponseFailed;
export type RefreshResponseSuccess = {
data: {
success: true;
accessToken: string;
refreshToken: string;
};
};
export type RefreshResponseFailed = {
data: {
success: false;
};
};
// The refresh token changes after each refresh request, the old token is invalidated.
Local Password
Registration
Is used to register a new user. The endpoint is /<prefix>/register
// Request
export type RegistrationRequest = {
email: string;
password: string;
name?: string;
};
// Responses information
export type RegistrationResponse = RegistrationResponseSuccess | RegistrationResponseFailure;
export type RegistrationResponseSuccess = {
data: {
success: true;
accessToken: string;
refreshToken: string;
};
};
export type RegistrationResponseFailure = {
data: {
success: false;
message: string;
};
};
The returned accessToken
can be used in requests to identify the new user.
The refreshToken
is used in the refresh
endpoint to obtain a new accessToken.
Login
Is used to login a user and obtain its credentials. The endpoint is /<prefix>/login
.
// Login request
export type LoginRequest = {
email: string;
password: string;
};
// Response
export type LoginResponse = LoginResponseSuccess | LoginResponseFailed;
export type LoginResponseSuccess = {
data: {
authorized: true;
accessToken: string;
refreshToken: string;
};
};
export type LoginResponseFailed = {
data: {
authorized: false;
message: string;
};
};
Change Password
This endpoint can be used to change the password for a user. It requires a valid accessToken in the Authorization: Bearer <token>
header.
Its location is /<prefix>/post
, and must be invoked using POST
.
If the provided original password is not correct, a 400 (Bad Request) response will be returned.
When the password is successfully changed all existing refreshTokens will be invalidated.
// Request
export type ChangePasswordRequest = {
email: string;
password: string;
newPassword: string;
};
// Response
export type ChangePasswordResponseSuccess = {
data: {
accessToken: string;
refreshToken: string;
};
};
Request Reset Password
This endpoint will generate a reset token for a user. Its path is /<prefix>/reset-password
. For this method
you should also register a requestResetPassword
hook, to send the URL with the identifier included to the user,
to start the flow.
If the endpoint is invoked twice, the code from the first request is invalidated, only the last generated reset token is valid.
// Request
export type RequestResetPasswordRequest = {
email: string;
};
// Response
// A HTTP 201 status code. The response will be the same, regardless if the user account
// has been found. The requestResetPassword hook will not be invoked if the account is not found.
Do Reset Password
When a user receives a reset password token, use this endpoint to use the token to change the password. Its endpoint
is /<prefix>/do-reset-password
. On success, it will return a new refresh and access token.
INVALID_TOKEN
is currently the only reason for the request failing.
All existing refresh tokens are invalidated.
// Request
export type DoResetPasswordRequest = {
password: string;
token: string;
};
export type DoResetPasswordResponseFailed = {
data: {
success: false;
reason: 'INVALID_TOKEN';
};
};
export type DoResetPasswordResponseSuccess = {
data: {
success: true;
accessToken: string;
refreshToken: string;
};
};
// Response
export type DoResetPasswordResponse = DoResetPasswordResponseFailed | DoResetPasswordResponseSuccess;
Magic Login
Request Magic Login
Is used to request a magic login token for an emailaddress. Its endpoint is /<prefix>/get-magic-login
.
export type RequestMagicLoginRequest = {
email: string;
};
The HTTP return code is HTTP 201 if the request was succesfully executed.
The configured `sendMagicIdentifier` callback that was configured is invoked
as part of this request.
Magic Login
Exchange a token generated by the /get-magic-login
endpoint for an
accessToken and refreshToken. The endpoint is /<prefix>/magic-login
.
export type MagicLoginRequest = {
identifier: string;
};
export type MagicLoginResponse = MagicLoginResponseSuccess | MagicLoginResponseFailure;
export type MagicLoginResponseSuccess = {
data: {
authorized: true;
accessToken: string;
refreshToken: string;
};
};
export type MagicLoginResponseFailure = {
data: {
authorized: false;
message?: string;
};
};
Development
Todo
- include the collection name in the logging in the hooks
- return user information on login and refresh
- return expiration information on login and refresh
- typings on addCollection
- logout endpoint
- Registration: Username validation / password length / strength
Contributing
Contributions are welcome. This does not necessarily have to be code, it can also be updated documentation, tutorials, bug reports or pull requests.
Please create an Issue to propose a feature you want to implement, so that the details can be discussed in advance.