mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	
							parent
							
								
									f4feab89f3
								
							
						
					
					
						commit
						fea601099a
					
				| @ -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
									
								
								examples/basic-auth-hook.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								examples/basic-auth-hook.js
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								examples/basic-auth-unleash.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								examples/basic-auth-unleash.js
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								examples/client-auth-unleash.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								examples/client-auth-unleash.js
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								examples/google-auth-hook.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								examples/google-auth-hook.js
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								examples/google-auth-unleash.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								examples/google-auth-unleash.js
									
									
									
									
									
										Normal file
									
								
							| @ -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')}` | ||||
|         ); | ||||
|     }); | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user