Express DID Auth - a challenge-response authentication model based in DIDs
This package is an implementation of the DID Auth protocol. It is designed to be easely integrated to any Express application.
Main features:
- Automatically set up the needed endpoints to fullfil the full protocol specification (signup, auth, refresh token and logout)
- Provides with an auth middleware to protect the desired business related endpoints
- Allows to decide where to send the tokens (cookies,
Authorizationheader or request body for refresh token) - Extensibility: it allows to add any specific business logic over the authentication/signup methods
- Deterministic challenge generation
- Limit requests per did per timeslot
Usage
Install
npm i @rsksmart/express-did-auth
Plug and play
This is the simplest approach. Just need to provide an express app and the desired configuration for the package and it will create the needed endpoints on your behalf.
import express from 'express'
import setupApp, { ExpressDidAuthConfig } from '@rsksmart/express-did-auth'
const config: ExpressDidAuthConfig = {
// your config
}
const app = express()
const authMiddleware = setupApp(config)(app)
app.get('/not-protected', function (req, res) {
res.send('This endpoint is not authenticating')
})
app.get('/protected', authMiddleware, function (req, res) {
res.send('This endpoint is authenticating')
})
const port = process.env.PORT || 5000
app.listen(port, () => logger.info(`My express API with did-auth running in ${port}`))
Configure
All the configuration should be placed in just one object of type ExpressDidAuthConfig. That object may contain the following fields:
REQUIRED
challengeSecret: string: the secret that will be used to generate the deterministic challenge. See how we create deterministic challenges
serviceUrl: string: will be used as the audience of all the JWTs expected or emitted by this package. Should be a URI that identifies your service in the context where it is run
serviceDid: string: the did controlled by the servie. Will be used to sign JWTs.
serviceSigner: Signer: the signing function associated to the serviceDid. MUST implement ES256K algorithm, please find an example here
OPTIONAL
useCookies: boolean: determines if the access token and refresh token are saved in cookies or are returned in the body of the response. If true, the tokens will be extracted from the cookies. See how to send tokens for more information. Default: false
requestSignupPath: string: the request signup endpoint route. Default: /request-signup
signupPath: string: the signup endpoint route. Default: /signup
requestAuthPath: string: the request auth endpoint route. Default: /request-auth
authPath: string: the auth endpoint route. Default: /auth
logoutPath: string: the logout endpoint route. Default: /logout
refreshTokenPath: string: the refresh token endpoint route. Default: /refresh-token
challengeExpirationTimeInSeconds: number: the max expiration time for the generated challenge when requesting signup or auth. MUST be provided in seconds. Default: 300 (5 minutes)
maxRequestsPerTimeSlot: number: the max amount of requests per did per timeslot. Default: 20
timeSlotInSeconds: number: the amount of seconds that need to elapse before resetting the request counter. Default: 600 (10 minutes)
userSessionDurationInHours: number: the validity of each refresh token in hours. Default: 168 (one week)
rpcUrl: string: rpc url used to resolve Ethr DID identities. If not provided, will resolve using both RSK Mainnet and Testnet networks.
networkName: string: network name used to resolve Ethr DID identities. If not provided, will resolve using both RSK Mainnet and Testnet networks.
registry: string: DID Registry address used to resolve Ethr DID identities. Default: 0xdca7ef03e98e0dc2b855be647c39abe984fcf21b
accessTokenExpirationTimeInSeconds: number: the validity in seconds of each access token. Remember that it should be short because the long validity is for the refresh token. Default: 600 (10 minutes)
authenticationBusinessLogic: AuthenticationBusinessLogic: the business logic to execute when a DID tries to log in. Will be executed each time the /auth endpoint is invoked with a valid signature. If it throws an error, the error message will be returned as part of an HTTP 401 response. If not present, no business logic will be executed.
requiredCredentials: string[]: array of Verifiable Credential schemas that will be requested as part of the signup process. If neither requiredCredentials and requiredClaims are present, no sdr will be requested when a user signs up.
requiredClaims: Claim[]: array of Claims that will be requested as part of the signup process. If neither requiredCredentials and requiredClaims are present, no sdr will be requested when a user signs up.
signupBusinessLogic: SignupBusinessLogic: the business logic to execute when a DID tries to sign up. It receives the required sdr as part of the payload. Will be executed each time the /signup endpoint is invoked with a valid signature. If it throws an error, the error message will be returned as part of an HTTP 401 response. Should be used to validate the sdr against the business needings and/or to save users in any storage for future authentication validation. If not present, no business logic will be executed.
Included artifacts
Endpoints
GET /request-signup/:did
Expects the user did in the params of the request.
Returns an HTTP 200 with a JSON containing { challenge, sdr? } in the body of the response.
The sdr will be present if the service requires it to register the user. See more information in the protocol.
Possible error messages:
INVALID_DID(HTTP 401)
POST /signup
Expects the signed challenge and sdr (if needed) response in the body of the request as { response }.
If using cookies, sets the cookies in the service and returns just an HTTP 200 with no content.
If not using cookies, returns an HTTP 200 with a JSON containing { accessToken, refreshToken }.
Possible error messages (all HTTP 401):
NO_RESPONSEifresponseis empty or does not existINVALID_CHALLENGEif the JWT verification fails or the received challenge is invalidUNAUTHORIZED_USERifsignupBusinessLogicdoes not validate the user- If the
signupBusinessLogicthrows an error, it will be sent to the client as well.
GET /request-auth/:did: { challenge }
Expects the user did in the params of the requests.
Returns an HTTP 200 with a JSON containing { challenge } in the body of the response.
Possible error messages:
INVALID_DID(HTTP 401)
POST /auth
Expects the signed challenge response in the body of the request as { response }.
If using cookies, sets the cookies in the service and returns just an HTTP 200 with no content to the client.
If not using cookies, returns an HTTP 200 with a JSON containing { accessToken, refreshToken }.
Possible error messages (all HTTP 401):
NO_RESPONSEifresponseis empty or does not existINVALID_CHALLENGEif the JWT verification fails or the received challenge is invalidUNAUTHORIZED_USERifauthenticationBusinessLogicdoes not validate the user- If the
authenticationBusinessLogicthrows an error, it will be sent to the client as well.
POST /refresh-token
If using cookies, will get the refresh token from a cookie.
If not, expects the refresh token in the body of the request as { refreshToken }.
Validates user session and:
- If using cookies, sets the new tokens cookies in the service and returns just an HTTP 200 to the client.
- If not using cookies, returns an HTTP 200 with a JSON containing the new tokens:
{ accessToken, refreshToken }.
Possible error messages (all HTTP 401):
NO_REFRESH_TOKENifrefreshTokencould not be extracted from the cookies nor the bodyINVALID_OR_EXPIRED_SESSIONif invalid refresh token or expired session
POST /logout
This is a protected endpoint, so the auth middleware is executed before.
It invalidates the current user session and returns an HTTP 200 with no content.
It has not own validations, so the error messages it may respond with comes from the middleware.
Auth Middleware
Authenticates requests by checking the access token.
If using cookies, will get it from a cookie.
If not, expects the access token in the Authorization header of the request with the DID Auth scheme (DIDAuth ${accessToken}).
It validates the extracted token and if it fullfils the protocol needs, it authenticates the request by injecting the user did in the request object so it is available to be used by the endpoint. The did will be injected under req.user.
Possible error messages (all HTTP 401):
INVALID_HEADERif not using cookies and the access token header does not follow theDIDAuthschemeNO_ACCESS_TOKENif access token could not be extracted from the cookies nor theAuthorizationheaderEXPIRED_ACCESS_TOKENif the access token has expiredINVALID_ACCESS_TOKENif the access tokennbftime is greater than current time
Run for development
The service source code is hosted in Github, so please refer directly to the README and check there the detailed guide to install and test the service locally.