-
Notifications
You must be signed in to change notification settings - Fork 123
Access Control
Access control is an important part to any application, especially one involving patient or medical information. In node-fhir-server-core
we make it easier to implement by offering several solutions. We have some built in options that allow you to adopt Smart on FHIR with scope checking, but you can also just give us your own passport strategy. If none of these fit what your looking for, we export the server class that we use to build the app which exposes express and you can completely roll your own solution with your own middleware and strategies. For more information, please read through the examples below.
We have built in support if you are using Smart on FHIR however this requires you to have an operational OAuth2 server. While we try to make this as simple as possible, you will need a few things. You will need to enable it in the config and give it service that uses a Smart on FHIR strategy. Below is a walkthrough of how to set this up.
When setting up your FHIR server, add the following config entries to your current configuration:
const FHIRServer = require('@asymmetrik/node-fhir-server-core');
let server = FHIRServer.initialize({
auth: {
// This is so we know you want scope validation, without this, we would only
// use the strategy
type: 'smart',
// Define our strategy here, for smart to work, we need the name to be bearer
// and to point to a service that exports a Smart on FHIR compatible strategy
strategy: {
name: 'bearer',
service: 'path/to/my/authentication.service.js'
}
}
});
And that is all that is needed as far as config goes, but let's take a look at how to define this authentication service we just configured next.
If you are not familiar with writing passport strategies that is fine, you can use @asymmetrik/sof-strategy
. To use this, create a file that looks like this:
const smartBearerStrategy = require('@asymmetrik/sof-strategy');
module.exports.strategy = smartBearerStrategy({
introspectionUrl: process.env.INTROSPECTION_URL,
clientSecret: process.env.CLIENT_SECRET,
clientId: process.env.CLIENT_ID
});
You will also need to add @asymmetrik/sof-strategy
as a dependency by running:
yarn add @asymmetrik/sof-strategy
And define some environment variables, INTROSPECTION_URL
, CLIENT_ID
, and CLIENT_SECRET
.
That's it, this will enable Smart on FHIR authentication and scope validation. This strategy will make a request to the introspection url with a bearer token, CLIENT_ID and CLIENT_SECRET. This will attempt to authenticate the user and store the token on req.user
so our scope middleware can validate the scopes for access to a requested resource.
If you just need to provide your own passport strategy and don't want to use Smart on FHIR, you can do so very easily. In this example, we'll use a Basic Strategy, but the setup can be used for any strategy.
In your config, add the following:
let server = FHIRServer.initialize({
auth: {
strategy: {
name: 'basic',
service: 'path/to/my/basic.service.js'
}
}
});
Then create the following basic.service.js
:
const { BasicStrategy } = require('passport-http');
module.exports.strategy = new BasicStrategy((username, password, done) => {
if (username === 'user' && password === 'pass') {
return done(null, {});
} else {
return done(new Error('Invalid Username/Password'));
}
});
And that's it, you now have basic auth setup. You would need to install passport-http
via yarn add passport-http
to make this work, but this is more of an example than something you should use in the real world. Please don't hardcode your username and password EVER.
There are some more advanced ways to setup the server. By default, if you do not provide any auth config, noop middleware will be used in it's place. So if you need to do more than what is provided through the config, you can.
In our Getting Started section, we showed you how to create a FHIR server by calling initialize
and giving it some config, like so.
const FHIRServer = require('@asymmetrik/node-fhir-server-core');
let server = FHIRServer.initialize({ ...someConfig });
What initialize is actually doing is just calling several internal methods. You can replicate the initialize method by doing the following:
const { Server } = require('@asymmetrik/node-fhir-server-core');
let server = new Server(config)
.configureMiddleware()
.configureSession()
.configureHelmet()
.configurePassport()
.setPublicDirectory()
.setProfileRoutes()
.setErrorRoutes();
This server instance exposes the express app instance via an app
property. So you can do the following:
const { Server } = require('@asymmetrik/node-fhir-server-core');
let server = new Server(config);
// Write your own custom middleware here for validating roles or using passport
server.app.use(function (req, res, next) {
});
// Call whichever of these functions you would like
server.configureMiddleware()
.configureSession()
.configureHelmet()
.setPublicDirectory()
.setProfileRoutes()
.setErrorRoutes();
// Finally start the server
server.listen(3000);