New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for authentication with Okta #299
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
'use strict'; | ||
|
||
// Load modules | ||
|
||
const Hapi = require('hapi'); | ||
const Hoek = require('hoek'); | ||
const Boom = require('boom'); | ||
|
||
const server = new Hapi.Server(); | ||
server.connection({ port: 8000 }); | ||
|
||
server.register([require('hapi-auth-cookie'), require('../')], (err) => { | ||
|
||
Hoek.assert(!err, err); | ||
server.auth.strategy('session', 'cookie', { | ||
password: 'secret_cookie_encryption_password', //Use something more secure in production | ||
redirectTo: '/auth/okta', //If there is no session, redirect here | ||
isSecure: false //Should be set to true (which is the default) in production | ||
}); | ||
|
||
server.auth.strategy('okta', 'bell', { | ||
provider: 'okta', | ||
password: 'cookie_encryption_password_secure', | ||
isSecure: false, | ||
location: 'http://127.0.0.1:8000', | ||
clientId: 'IIA1yMR7IK4XGhfyfCno', | ||
clientSecret: 'PEh_HemJovaR-Zjs-unX8-cC9IhQgzF5M1RUrUgW' | ||
}); | ||
|
||
server.route({ | ||
method: 'GET', | ||
path: '/auth/okta', | ||
config: { | ||
auth: 'okta', | ||
handler: function (request, reply) { | ||
|
||
if (!request.auth.isAuthenticated) { | ||
return reply(Boom.unauthorized('Authentication failed: ' + request.auth.error.message)); | ||
} | ||
|
||
//Just store the third party credentials in the session as an example. You could do something | ||
//more useful here - like loading or setting up an account (social signup). | ||
request.auth.session.set(request.auth.credentials); | ||
|
||
return reply.redirect('/'); | ||
} | ||
} | ||
}); | ||
|
||
server.route({ | ||
method: 'GET', | ||
path: '/', | ||
config: { | ||
auth: 'session', | ||
handler: function (request, reply) { | ||
|
||
//Return a message using the information from the session | ||
return reply('Hello, ' + request.auth.credentials.profile.email + '!'); | ||
} | ||
} | ||
}); | ||
|
||
server.start((err) => { | ||
|
||
Hoek.assert(!err, err); | ||
console.log('Server started at:', server.info.uri); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,38 @@ | ||
'use strict'; | ||
|
||
exports = module.exports = { | ||
arcgisonline: require('./arcgisonline'), | ||
auth0: require('./auth0'), | ||
azuread: require('./azuread'), | ||
bitbucket: require('./bitbucket'), | ||
discord: require('./discord'), | ||
dropbox: require('./dropbox'), | ||
facebook: require('./facebook'), | ||
fitbit: require('./fitbit'), | ||
foursquare: require('./foursquare'), | ||
github: require('./github'), | ||
gitlab: require('./gitlab'), | ||
googleplus: require('./googleplus'), | ||
google: require('./google'), | ||
googleplus: require('./googleplus'), | ||
instagram: require('./instagram'), | ||
linkedin: require('./linkedin'), | ||
live: require('./live'), | ||
medium: require('./medium'), | ||
meetup: require('./meetup'), | ||
nest: require('./nest'), | ||
slack: require('./slack'), | ||
spotify: require('./spotify'), | ||
twitter: require('./twitter'), | ||
vk: require('./vk'), | ||
yahoo: require('./yahoo'), | ||
arcgisonline: require('./arcgisonline'), | ||
office365: require('./office365'), | ||
okta: require('./okta'), | ||
phabricator: require('./phabricator'), | ||
pingfed: require('./pingfed'), | ||
pinterest: require('./pinterest'), | ||
reddit: require('./reddit'), | ||
meetup: require('./meetup'), | ||
salesforce: require('./salesforce'), | ||
slack: require('./slack'), | ||
spotify: require('./spotify'), | ||
tumblr: require('./tumblr'), | ||
twitch: require('./twitch'), | ||
salesforce: require('./salesforce'), | ||
twitter: require('./twitter'), | ||
vk: require('./vk'), | ||
wordpress: require('./wordpress'), | ||
office365: require('./office365'), | ||
pinterest: require('./pinterest'), | ||
pingfed: require('./pingfed'), | ||
discord: require('./discord') | ||
yahoo: require('./yahoo') | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
'use strict'; | ||
|
||
// Load modules | ||
const Joi = require('joi'); | ||
const Hoek = require('hoek'); | ||
|
||
// Declare internals | ||
|
||
const internals = { | ||
schema: Joi.object({ | ||
uri: Joi.string().uri().required() | ||
}).required() | ||
}; | ||
|
||
exports = module.exports = function (options) { | ||
|
||
const results = Joi.validate(options, internals.schema); | ||
Hoek.assert(!results.error, results.error); | ||
const settings = results.value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should you not assert there is an uri since you do not provide a default? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is following a very similar pattern as https://github.com/hapijs/bell/blob/master/lib/providers/auth0.js#L12. It was my understanding that line 17 using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I totally missed the |
||
|
||
return { | ||
protocol: 'oauth2', | ||
useParamsAuth: true, | ||
auth: settings.uri + '/oauth2/v1/authorize', | ||
token: settings.uri + '/oauth2/v1/token', | ||
scope: ['openid', 'email', 'offline_access'], | ||
profile: function (credentials, params, get, callback) { | ||
|
||
get(settings.uri + '/oauth2/v1/userinfo', null, (profile) => { | ||
|
||
credentials.profile = { | ||
id: profile.sub, | ||
username: profile.email, | ||
displayName: profile.nickname, | ||
firstName: profile.given_name, | ||
lastName: profile.family_name, | ||
email: profile.email, | ||
raw: profile | ||
}; | ||
|
||
return callback(); | ||
}); | ||
} | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ | |
"nest", | ||
"phabricator", | ||
"office365", | ||
"okta", | ||
"reddit", | ||
"tumblr", | ||
"twitter", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
'use strict'; | ||
|
||
// Load modules | ||
|
||
const Bell = require('../../'); | ||
const Code = require('code'); | ||
const Hapi = require('hapi'); | ||
const Hoek = require('hoek'); | ||
const Lab = require('lab'); | ||
const Mock = require('../mock'); | ||
|
||
// Test shortcuts | ||
|
||
const lab = exports.lab = Lab.script(); | ||
const describe = lab.describe; | ||
const it = lab.it; | ||
const expect = Code.expect; | ||
|
||
describe('Okta', () => { | ||
|
||
it('fails with no uri', { parallel: false }, (done) => { | ||
|
||
const mock = new Mock.V2(); | ||
mock.start((provider) => { | ||
|
||
const server = new Hapi.Server(); | ||
server.connection({ host: 'localhost', port: 80 }); | ||
server.register(Bell, (err) => { | ||
|
||
expect(err).to.not.exist(); | ||
|
||
expect(Bell.providers.okta).to.throw(Error); | ||
|
||
mock.stop(done); | ||
}); | ||
}); | ||
}); | ||
|
||
it('authenticates with mock and custom uri', { parallel: false }, (done) => { | ||
|
||
const mock = new Mock.V2(); | ||
mock.start((provider) => { | ||
|
||
const server = new Hapi.Server(); | ||
server.connection({ host: 'localhost', port: 80 }); | ||
server.register(Bell, (err) => { | ||
|
||
expect(err).to.not.exist(); | ||
|
||
const custom = Bell.providers.okta({ uri: 'http://example.com' }); | ||
|
||
expect(custom.auth).to.equal('http://example.com/oauth2/v1/authorize'); | ||
expect(custom.token).to.equal('http://example.com/oauth2/v1/token'); | ||
|
||
Hoek.merge(custom, provider); | ||
|
||
const profile = { | ||
sub: '1234567890', | ||
nickname: 'steve_smith', | ||
given_name: 'steve', | ||
middle_name: 'jared', | ||
family_name: 'smith', | ||
email: 'steve@example.com' | ||
}; | ||
|
||
Mock.override('http://example.com/oauth2/v1/userinfo', profile); | ||
|
||
server.auth.strategy('custom', 'bell', { | ||
password: 'cookie_encryption_password_secure', | ||
isSecure: false, | ||
clientId: 'okta', | ||
clientSecret: 'secret', | ||
provider: custom | ||
}); | ||
|
||
server.route({ | ||
method: '*', | ||
path: '/login', | ||
config: { | ||
auth: 'custom', | ||
handler: function (request, reply) { | ||
|
||
reply(request.auth.credentials); | ||
} | ||
} | ||
}); | ||
|
||
server.inject('/login', (res) => { | ||
|
||
const cookie = res.headers['set-cookie'][0].split(';')[0] + ';'; | ||
mock.server.inject(res.headers.location, (mockRes) => { | ||
|
||
server.inject({ url: mockRes.headers.location, headers: { cookie } }, (response) => { | ||
|
||
Mock.clear(); | ||
expect(response.result).to.equal({ | ||
provider: 'custom', | ||
token: '456', | ||
expiresIn: 3600, | ||
refreshToken: undefined, | ||
query: {}, | ||
profile: { | ||
id: '1234567890', | ||
username: 'steve@example.com', | ||
displayName: 'steve_smith', | ||
firstName: 'steve', | ||
lastName: 'smith', | ||
email: 'steve@example.com', | ||
raw: profile | ||
} | ||
}); | ||
mock.stop(done); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes my brain smile 👌