Skip to content

Commit

Permalink
Document how to secure Unleash.
Browse files Browse the repository at this point in the history
closes: #233
  • Loading branch information
ivaosthu committed Jan 17, 2018
1 parent 323320b commit 04e94b2
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 2 deletions.
72 changes: 70 additions & 2 deletions docs/securing-unleash.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,71 @@
# Securing Unleash
# Secure Unleash
The Unleash API is split in two different paths: `/api/client` and `/api/admin`.
This makes it easy to have different authentication strategy for the admin interface and the client-api used by the applications integrating with Unleash.

TODO: write about how to secure `/api/client` and `/api/admin`
## General settings
Unleash uses an encrypted cookie to maintain a user session. This allows users to be logged in across instances of Unleash. To protect this cookie you should specify the `secret` option when starting unleash.-

## Securing the Admin API
In order to secure the Admin API you have to tell Unleash that you are using a custom admin authentication and implement your authentication logic as a preHook. You should also set the secret option to a protected secret in your system.

```javascript
const unleash = require('unleash-server');
const myCustomAdminAuth = require('./auth-hook');

unleash.start({
databaseUrl: 'postgres://unleash_user:passord@localhost:5432/unleash',
secret: 'super-duper-secret',
adminAuthentication: 'custom',
preRouterHook: myCustomAdminAuth
}).then(unleash => {
console.log(`Unleash started on http://localhost:${unleash.app.get('port')}`);
});

```

Examples on custom authentication hooks:
- [google-auth-hook.js](https://github.com/Unleash/unleash/blob/master/examples/google-auth-hook.js)
- [basic-auth-hook.js](https://github.com/Unleash/unleash/blob/master/examples/basic-auth-hook.js)


## Securing the Client API
A common way to support client access is to use pre shared secrets. This can be solved by having clients send a shared key in a http header with every client requests to the Unleash API. All official Unleash clients should support this.

In the [Java client](https://github.com/Unleash/unleash-client-java#custom-http-headers) this looks like:

```java
UnleashConfig unleashConfig = UnleashConfig.builder()
.appName("my-app")
.instanceId("my-instance-1")
.unleashAPI(unleashAPI)
.customHttpHeader("Authorization", "12312Random")
.build();
```

On the unleash server side you need to implement a preRouterHook hook which verifies that all calls to `/api/client` includes this pre shared key in the defined header. This could look something like this:

```javascript
const unleash = require('unleash-server');
const sharedSecret = '12312Random';

unleash.start({
databaseUrl: 'postgres://unleash_user:passord@localhost:5432/unleash',
enableLegacyRoutes: false,
preRouterHook: (app) => {
app.use('/api/client', (req, res, next) => {
if(req.headers.authorization !== sharedSecret) {
res.sendStatus(401);
} else {
next()
}
});
}
}).then(unleash => {
console.log(`Unleash started on http://localhost:${unleash.app.get('port')}`);
});
```

[client-auth-unleash.js](https://github.com/Unleash/unleash/blob/master/examples/client-auth-unleash.js)


PS! Remember to disable legacy route with by setting the `enableLegacyRoutes` option to false. This will require all your clients to be on v3.x.
30 changes: 30 additions & 0 deletions examples/basic-auth-hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

const auth = require('basic-auth');
const { User } = require('../lib/server-impl.js');

function basicAuthentication(app) {
app.use('/api/admin/', (req, res, next) => {
const credentials = auth(req);

if (credentials) {
// you will need to do some verification of credentials here.
const user = new User({ email: `${credentials.name}@domain.com` });
req.user = user;
next();
} else {
return res
.status('401')
.set({ 'WWW-Authenticate': 'Basic realm="example"' })
.end('access denied');
}
});

app.use((req, res, next) => {
// Updates active sessions every hour
req.session.nowInHours = Math.floor(Date.now() / 3600e3);
next();
});
}

module.exports = basicAuthentication;
19 changes: 19 additions & 0 deletions examples/basic-auth-unleash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';

// const unleash = require('unleash-server');
const unleash = require('../lib/server-impl.js');

const basicAuth = require('./basic-auth-hook');

unleash
.start({
databaseUrl: 'postgres://unleash_user:passord@localhost:5432/unleash',
secret: 'super-duper-secret',
adminAuthentication: 'custom',
preRouterHook: basicAuth,
})
.then(server => {
console.log(
`Unleash started on http://localhost:${server.app.get('port')}`
);
});
27 changes: 27 additions & 0 deletions examples/client-auth-unleash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

// const unleash = require('unleash-server');
const unleash = require('../lib/server-impl.js');

// You typically will not hard-code this value in your code!
const sharedSecret = '12312Random';

unleash
.start({
databaseUrl: 'postgres://unleash_user:passord@localhost:5432/unleash',
enableLegacyRoutes: false,
preRouterHook: app => {
app.use('/api/client', (req, res, next) => {
if (req.headers.authorization === sharedSecret) {
next();
} else {
res.sendStatus(401);
}
});
},
})
.then(server => {
console.log(
`Unleash started on http://localhost:${server.app.get('port')}`
);
});
85 changes: 85 additions & 0 deletions examples/google-auth-hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict';

/**
* Google OAath 2.0
*
* You should read Using OAuth 2.0 to Access Google APIs:
* https://developers.google.com/identity/protocols/OAuth2
*
* This example assumes that all users authenticating via
* google should have access. You would proably limit access
* to users you trust.
*
* The implementation assumes the following environement variables:
*
* - GOOGLE_CLIENT_ID
* - GOOGLE_CLIENT_SECRET
* - GOOGLE_CALLBACK_URL
*/

// const { User, AuthenticationRequired } = require('unleash-server');
const { User, AuthenticationRequired } = require('../lib/server-impl.js');

const passport = require('passport');
const GoogleOAuth2Strategy = require('passport-google-auth').Strategy;

passport.use(
new GoogleOAuth2Strategy(
{
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL,
},

(accessToken, refreshToken, profile, done) => {
done(
null,
new User({
name: profile.displayName,
email: profile.emails[0].value,
})
);
}
)
);

function enableGoogleOauth(app) {
app.use(passport.initialize());
app.use(passport.session());

passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));
app.get('/api/admin/login', passport.authenticate('google'));

app.get(
'/api/auth/callback',
passport.authenticate('google', {
failureRedirect: '/api/admin/error-login',
}),
(req, res) => {
// Successful authentication, redirect to your app.
res.redirect('/');
}
);

app.use('/api/admin/', (req, res, next) => {
if (req.user) {
next();
} else {
// Instruct unleash-frontend to pop-up auth dialog
return res
.status('401')
.json(
new AuthenticationRequired({
path: '/api/admin/login',
type: 'custom',
message: `You have to identify yourself in order to use Unleash.
Click the button and follow the instructions.`,
})
)
.end();
}
});
}

module.exports = enableGoogleOauth;
19 changes: 19 additions & 0 deletions examples/google-auth-unleash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';

// const unleash = require('unleash-server');
const unleash = require('../lib/server-impl.js');

const enableGoogleOauth = require('./google-auth-hook');

unleash
.start({
databaseUrl: 'postgres://unleash_user:passord@localhost:5432/unleash',
secret: 'super-duper-secret',
adminAuthentication: 'custom',
preRouterHook: enableGoogleOauth,
})
.then(server => {
console.log(
`Unleash started on http://localhost:${server.app.get('port')}`
);
});

0 comments on commit 04e94b2

Please sign in to comment.