Server Side (DID Auth)
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,
Authorization
header 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. Please check out this note about using cookies if turning on this feature. Default: false
allowMultipleSessions: boolean
: if useCookies
is true
, then this flag allow the user to maintain multiple sessions for different dids in the same browser. In order to do that, the user must add the x-logged-did
header in each request with the desired value, so the service will now which cookie to recover for each request. Please check out this note about using cookies if turning on this feature. 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
loginMessageHeader: string
: the message header that is expected to be received when the user signs the login message, it will be used to recover the signer against the received message. If not provided, the expected message will be just URL: <service url>\nVerification code: <expected challenge>
, without any header before the URL
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 (sign up) the user. See more information in the sign up 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_RESPONSE
ifresponse
is empty or does not existINVALID_CHALLENGE
if the JWT verification fails or the received challenge is invalidUNAUTHORIZED_USER
ifsignupBusinessLogic
does not validate the user- If the
signupBusinessLogic
throws 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_RESPONSE
ifresponse
is empty or does not existINVALID_CHALLENGE
if the JWT verification fails or the received challenge is invalidUNAUTHORIZED_USER
ifauthenticationBusinessLogic
does not validate the user- If the
authenticationBusinessLogic
throws 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_TOKEN
ifrefreshToken
could not be extracted from the cookies nor the bodyINVALID_OR_EXPIRED_SESSION
if 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_HEADER
if not using cookies and the access token header does not follow theDIDAuth
schemeNO_ACCESS_TOKEN
if access token could not be extracted from the cookies nor theAuthorization
headerEXPIRED_ACCESS_TOKEN
if the access token has expiredINVALID_ACCESS_TOKEN
if the access tokennbf
time is greater than current time
Note about cookies
In order to prevent CSRF attacks we've decided to use csurf package.
It adds a token in the header of each response that should be sent back in the next request. It can be found under the key x-csrf-token
. If the token is not added in the next request, the service will return a 503 Forbidden
response.
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.