@envage/defra-identity-hapi-plugin
Advanced tools
Weekly downloads
Changelog
5.2.9 - 16 Aug 2021
Readme
See the change log for a complete list of deprecations and changes
Note:
The Defra.Identity Hapi Plugin (DIHP) is designed to streamline and standardise the way Defra services interact with an OpenID Connect (OIDC) Identity Provider (IdP).
You can read more about OpenID Connect here.
Before you use DIHP, there are a few things you will need. If you do not have the following items, please contact the identity team to begin the onboarding process.
For registration:
For enrolment and fetching of user information:
You can find a demo implementation of this plugin in our demo service.
This plugin is available from the DEFRA npm organisation account:
npm install @envage/defra-identity-hapi-plugin --save
Generic docs about how to implement hapi auth plugins can be found here.
The full set of configuration options, and their defining schemas can be found in lib/config/schema.js.
You can see what values are applied by default in lib/config/defaults.js.
Example implementation with required config values:
const {
IDENTITY_APP_URL,
IDENTITY_SERVICEID,
IDENTITY_COOKIEPASSWORD,
IDENTITY_CLIENTID,
IDENTITY_CLIENTSECRET,
IDENTITY_DEFAULT_POLICY,
IDENTITY_DEFAULT_JOURNEY,
AAD_AUTHHOST,
AAD_TENANTNAME,
DYNAMICS_AADCLIENTID,
DYNAMICS_AADCLIENTSECRET,
DYNAMICS_RESOURCEURL,
DYNAMICS_ENDPOINTBASE
} = process.env
await server.register({
plugin: require('@envage/defra-identity-hapi-plugin'),
options: {
appDomain: `http://${HOST}:${PORT}`, // This is the domain your application is exposed through. This is used to form part of the url the user will be redirected back to after authentication
identityAppUrl: IDENTITY_APP_URL,
serviceId: IDENTITY_SERVICEID,
cookiePassword: IDENTITY_COOKIEPASSWORD,
clientId: IDENTITY_CLIENTID,
clientSecret: IDENTITY_CLIENTSECRET,
defaultPolicy: IDENTITY_DEFAULT_POLICY,
defaultJourney: IDENTITY_DEFAULT_JOURNEY,
isSecure: false, // Set this if without https - i.e. localhost,
aad: {
authHost: AAD_AUTHHOST,
tenantName: AAD_TENANTNAME
},
dynamics: {
clientId: DYNAMICS_AADCLIENTID,
clientSecret: DYNAMICS_AADCLIENTSECRET,
resourceUrl: DYNAMICS_RESOURCEURL,
endpointBase: DYNAMICS_ENDPOINTBASE
}
}
})
By default, the onByDefault
option is false. So in the example above, to secure a route with DIHP, you will need to enable auth for that specific route.
For example:
server.route({
method: 'GET',
path: '/',
options: {
auth: 'idm'
},
handler: async function (request, h) {
return 'Hello world'
}
})
You can enable auth for every route by passing onByDefault
as true. If you do this, you will need to specify which routes you don't want auth enabled on.
For example:
server.route({
method: 'GET',
path: '/',
options: {
auth: false
},
handler: async function (request, h) {
return 'Hello world'
}
})
By default, DIHP uses an in memory cache. This is useful for getting an implementation up and running quickly in development. In production you can pass a cache
object to the config. This can be any type of cache implementation, as long as the object you pass in adheres to the following api:
{
get: async (key, request = undefined) => {},
set: async (key, value, ttl, request = undefined) => {}
drop: async (key, request = undefined) => {}
}
This is the same interface as the built in hapi cache. An example implementation can be found in demo/server.js
.
Note: If you need the request object to be passed into your caching methods, you need to set the config option passRequestToCacheMethods
as true when registering the plugin. This may be useful if you want to use client side caching.
DIHP uses hapi-auth-cookie to manage its cookies. DIHP will use this to store an encrypted reference to the users claims, stored in the plugin's cache.
This reference does not include any user information, at no point does the plugin expose any user information to the client's browser.
You can specify the name of the cookie set on the user's browser by passing in cookieName
.
You must also pass in cookiePassword
. It is a required field, that must be at least 32 characters long. This password is used to encrypt the data in the cookie.
The following routes are exposed by the plugin. All route paths are customisable when instantiating the plugin
server.methods.idm.generateOutboundRedirectUrl
with parameters contained within the url and and redirects the user to the url returnedAt present, the plugin exposes functionality to refresh the user's token, but it does not do it automatically. It is up to the service to decide when to check the validity of the claims and execute the refresh function
For example:
server.route({
method: 'GET',
path: '/',
handler: async function (request, h) {
// Fetch the user's credentials from the cache
const creds = await server.methods.idm.getCredentials(request)
// If the user has credentials and they are expired, call the refresh method
if (creds && creds.isExpired()) {
await server.methods.idm.refreshToken(request)
}
return 'Hello world'
}
})
You could also tell hapi to check the user's token at specific point in the request lifecycle.
For example:
server.ext('onPreAuth', async (request, h) => {
const { idm } = request.server.methods
const creds = await idm.getCredentials(request)
if (creds && creds.isExpired()) {
try {
await idm.refreshToken(request)
} catch (e) {
console.error(e)
}
}
return h.continue
})
Note: This will execute for every single request to every route in your application, including static files. See demo/server.js
for an example of how you could only check the refresh token for requests to actual routes.
DIHP uses OIDC's 'state' capability to be able to match up users it has sent to the IdP. This means that just before the user is sent to the IdP, a guid is generated, which is sent to the IdP, and stored locally in the cache. When the user returns from the IdP, the state is returned with them. The state returned is matched with the entry in the cache to retrieve some persisted journey data.
This persisted journey data includes:
It is important to send the user to B2C via the Outbound path exposed by DIHP. It is where the cache is populated with the above information.
You can generate an outbound url by executing the idm.generateAuthenticationUrl
server method detailed in server methods.
For example, you could generate an authentication url in your route handler and pass it to your view render function, like so:
Route handler
server.route({
method: 'GET',
path: '/',
options: {
auth: 'idm'
},
handler: async function (request, h) {
return h.view('index', {
authenticationUrl: server.methods.idm.generateAuthenticationUrl('/account')
})
}
})
View file
<a href="<%= authenticationUrl %>">Click here to log in</a>
When a user visits your service for the first time, you must create an association between their contact record and your service. This is to indicate that the user in question has visited your service. The status of this enrolment can indicate to helpdesk personnel and to your service whether the user is allowed access or not.
You can find an example of the enrolment procedure in demo/routes/enrolment.js.
Note: In versions <4 you were restricted to setting the user's enrolment status to either "incomplete" or "pending" on creation. As of v4.0.0 this is no longer the case.
The available enrolment statuses are:
The ids associated with the above statuses can be referenced by the server method getMappings
These enrolment statuses are not assigned specifically to an individual. They are provided to an individual on behalf of an organisation, for a specific role.
A user could be an employee of multiple organisations, but have a complete approved status for one role for one organisation, but a rejected status for the same (or a different) role for another organisation. It is important to remember to set the correct enrolment statuses for each role and for each organisation.
For example, a user may have the following set of roles. Note the multiple different statuses between the organisations and roles:
- Organisation 1
- Manager role
- Status: Complete - approved
- Data reader role
- Status: Pending
- Organisation 2
- Manager role
- Status: Completed - rejected
- User administrator
- Status: Incomplete
The following server methods will be created by the plugin, for consumption inside or outside of the plugin. You can read more about server methods here.
All server methods, with jsdocs can be found in lib/methods
idm.getCredentials
idm.getClaims
idm.generateAuthenticationUrl
idm.logout
idm.refreshToken
idm.generateOutboundRedirectUrl
idm.getCache
idm.getConfig
idm.getInternals
idm.dynamics.getMappings
server.methods.idm.dynamics.getMappings().enrolmentStatus.completeApproved
idm.dynamics.getToken
idm.dynamics.parseAuthzRoles
roles
and roleMappings
arrays from the user's JWT and returns an object of formatted rolesidm.dynamics.updateEnrolmentStatus
idm.dynamics.readCompanyNumbers
idm.dynamics.readContacts
idm.dynamics.readContactsAccountLinks
idm.dynamics.readEnrolment
idm.getClaims
idm.dynamics.readServiceRoles
idm.dynamics.createEnrolment
If you have an idea you'd like to contribute please log an issue.
All contributions should be submitted via a pull request.
Please note that the codebase conforms to the Jaavascript Standard Style.
Please make sure to run npm test
and npm run lint
before opening any pull requests.
THIS INFORMATION IS LICENSED UNDER THE CONDITIONS OF THE OPEN GOVERNMENT LICENCE found at:
http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3
The following attribution statement MUST be cited in your products and applications when using this information.
Contains public sector information licensed under the Open Government license v3
The Open Government Licence (OGL) was developed by the Controller of Her Majesty's Stationery Office (HMSO) to enable information providers in the public sector to license the use and re-use of their information under a common open licence.
It is designed to encourage use and re-use of information freely and flexibly, with only a few conditions.
FAQs
A hapi auth plugin to allow easy integration with DEFRA's Identity Management system
The npm package @envage/defra-identity-hapi-plugin receives a total of 269 weekly downloads. As such, @envage/defra-identity-hapi-plugin popularity was classified as not popular.
We found that @envage/defra-identity-hapi-plugin demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 15 open source maintainers collaborating on the project.
Did you know?
Socket installs a Github app to automatically flag issues on every pull request and report the health of your dependencies. Find out what is inside your node modules and prevent malicious activity before you update the dependencies.