API authorization flow with Hapijs and oauth 2 - oauth-2.0

We are using hapijs and oauth server for authentication. we need to implement role based authorization in hapijs. is below way is fine for hapijs.
Register authentication scheme
server.auth.scheme('custom', function (server, options) {
return {
authenticate: function (request, reply) {
// calling oauth flow for roles match
}
});
Register authentication strategy & adding auth,roles in server.route
server.auth.strategy('default', 'custom');
server.route({
method: 'GET',
path: API_Path,
config: {
roles: ['ADMIN', 'USER'],
auth : 'default'
},
handler: function (request, reply) {
return reply.act({
role: 'admin',
cmd: 'getInfo',
id: request.params.id
});
}
});

You should register a strategy and validate the credentials like this.
const validate = function (request, username, password, callback) {
const user = users[username];
if (!user) {
return callback(null, false);
}
Bcrypt.compare(password, user.password, (err, isValid) => {
callback(err, isValid, { id: user.id, name: user.name });
});
};
server.auth.strategy(strategy_name, scheme_name, { validateFunc: validate });
Your auth should have options like mode, scope, strategy etc
...
...
auth :{
mode:'required',
strategy:'strategy_name',
scope: ['ADMIN', 'USER']
},
...
...
You can use Bell

Related

Nuxt auth-next with keycloak CORS problem

Versions:
keycloak 12.0.2
nuxt: 2.14.6
nuxt/auth-next: 5.0.0-1622918202.e815752
Configs:
nuxt.config.js
auth: {
strategies: {
keycloak: {
scheme: '~/plugins/keycloak.js',
endpoints: {
authorization:'https://keycloak.bgzchina.com/auth/realms/bgzchina/protocol/openid-connect/auth',
token:'https://keycloak.bgzchina.com/auth/realms/bgzchina/protocol/openid-connect/token',
userInfo: "https://keycloak.bgzchina.com/auth/realms/bgzchina/protocol/openid-connect/token",
logout:'https://keycloak.bgzchina.com/auth/realms/bgzchina/protocol/openid-connect/logout',
},
responseType: 'id_token token',
clientId: 'centuari-portal-fe',
scope: ['openid'],
}
},
redirect: {
login: '/login',
logout: '/logout',
callback: '/callback',
home: '/',
}
},
router: {
middleware: ['auth']
},
due to a issue with current version nuxt/auth-next, I created a custom scheme by extending oauth2
/plugin/keycloak.js
import { Oauth2Scheme } from '~auth/runtime'
function encodeQuery(queryObject) {
return Object.entries(queryObject)
.filter(([_key, value]) => typeof value !== 'undefined')
.map(([key, value]) => encodeURIComponent(key) + (value != null ? '=' + encodeURIComponent(value) : ''))
.join('&')
}
export default class KeycloakScheme extends Oauth2Scheme {
logout() {
if (this.options.endpoints.logout) {
const opts = {
client_id: this.options.clientId,
post_logout_redirect_uri: this._logoutRedirectURI
}
const url = this.options.endpoints.logout + '?' + encodeQuery(opts)
window.location.replace(url)
}
return this.$auth.reset()
}
}
but when doing login, browser will block the token request due to CORS. keycloak server response for the preflight specify allowed method is POST, OPTIONS, but auth-next use GET to fetch token.
Is there any work around ?
You need to add/register the url into keycloak admin dashboard.
Go to keycloak admin dashboard
Menu Clients => select the client
On Settings tab, scroll down the page and find Web Origins. Add your frontend url (nuxt url) on it. Don't forget to add into Valid Redirect URIs too.

Laravel Passport authentication : how to use code and state?

I'm authenticating to Laravel (7.3) Passport with the following configuration:
nuxt.config.js
auth: {
redirect: {
login: '/login',
logout: '/login',
home: '/'
},
strategies: {
'laravel.passport': {
url: 'http://laravel.test',
client_id: '2',
client_secret: 'S0gpcgfIDgbvIHCL3jIhSICAiTsTUMOR0k5mdaCi',
redirect_uri: 'http://localhost:3000'
}
}
}
Authentication method in pages/login.vue:
async nuxtLaravelPassport() {
try {
const response = await this.$auth
.loginWith('laravel.passport')
.then(result => {
console.log(result)
})
} catch (err) {
console.log(err)
}
},
It brings me to the authentication page of Laravel, then I log in and I'm redirected to my Nuxt.js home page with a code and state as parameters.
What should I do with these code and state ? Get a token ? If yes, how ?

sessionConfig.perform not being called

I am trying to write a session authentication mechanism for my application, which goes like that:
import { ZObject, Bundle } from "zapier-platform-core";
import IAuthenticationScheme from "../interfaces/authentication/IAuthenticationScheme";
const getSessionKey = (z: ZObject, bundle: Bundle) => {
console.log('GET SESSION called');
const { username: auth_login, password: auth_password } = bundle.authData;
return z.request({
method: 'POST',
url: 'http://******/perl/auth/login',
body: { auth_login, auth_password }
}).then(response => {
z.console.log(response);
console.log(response);
if (response.status === 401) {
throw new Error('The username/password you supplied is invalid');
} else {
return {
sessionKey: z.JSON.parse(response.content).session_id
};
}
});
};
const includeSessionKeyHeader = (request: any, z: ZObject, bundle: Bundle) => {
console.log('includeSessionKeyHeader called');
if (bundle.authData.sessionKey) {
request.headers = Object.assign({}, request.headers);
let { Cookie: cookie = '' } = request.headers;
cookie = `${bundle.authData.sessionKey};${cookie}`;
request.headers['Cookie'] = cookie;
}
return request;
};
const sessionRefreshIf401 = (response: any, z: ZObject, bundle: Bundle) => {
console.warn('sessionRefreshIf401 called');
if (bundle.authData.sessionKey) {
if (response.status === 401) {
throw new z.errors.RefreshAuthError(); // ask for a refresh & retry
}
}
return response;
};
const test = (z: ZObject, bundle: Bundle) => {
console.log('test called');
return z.request({
url: 'http://******/ruby/features'
}).then((response) => {
z.console.log(response);
if (response.status === 401) {
throw new Error('The API Key you supplied is invalid');
}
return response
});
};
const authentication: IAuthenticationScheme<any> = {
type: 'session',
test,
fields: [
{
key: 'username',
type: 'string',
required: true,
helpText: 'Your login username.'
},
{
key: 'password',
type: 'string',
required: true,
helpText: 'Your login password.'
}
],
connectionLabel: (z, bundle) => {
return bundle.inputData.username;
},
sessionConfig: {
perform: getSessionKey
}
};
export default {
authentication,
beforeRequest: { includeSessionKeyHeader },
afterRequest: { sessionRefreshIf401 }
};
As you can see, I put console.log markers at the beginning of each function here so I can see in which order they are getting called.
Here is my test configuration:
import { should } from "should";
import { describe } from "mocha";
const { version } = require("../../package.json");
import { version as platformVersion } from "zapier-platform-core";
import { createAppTester } from "zapier-platform-core";
import PlackSession from "../authentication/PlackSession";
const App = {
version,
platformVersion,
authentication: PlackSession.authentication,
beforeRequest: [PlackSession.beforeRequest.includeSessionKeyHeader],
afterResponse: [PlackSession.afterRequest.sessionRefreshIf401],
};
const appTester = createAppTester(App);
export default () => {
describe('PlackSession authentication', () => {
it('should authenticate', done => {
console.log(`AUTHENTICATE!!`)
const bundle = {
authData: {
username: 'dev#******.com',
password: 'abc123'
}
};
appTester(App.authentication.test, bundle)
.then(response => {
console.log('BBBBBBBB')
done();
})
.catch(a => {
console.log('CCCCCC');
done(a)
});
});
});
};
And I can see the test output the logs in the following order:
authentication
PlackSession authentication
AUTHENTICATE!!
test called
includeSessionKeyHeader called
CCCCCC
1) should authenticate
That means sessionConfig.perform (getSessionKey) is never called, and this is where the credentials should be exchanged for authentication through the login API call, which I can also see in my server logs it never gets called, it skips straight to the test call and fails.
David here, from the Zapier Platform team. Great question!
I think the problem lies in your test. There should be two function. One should call App.authentication.sessionConfig.perform and tests exchanging username & password for a token. Another should call App.authentication.test, which tests fetching a protected resource with a valid key. Though these may be able to be chained together, they can also be written separately.
There's a more complete example here: https://github.com/zapier/zapier-platform-example-app-session-auth/blob/cc63ca67cbc8933439577b2362d026ba2a701e36/test/basic.js

MS Graph Sample Application Integration Test not Working

I want to do what the MS Graph sample node app is doing in its integrationTests.js, but that test doesn't work. Here's what I've tried:
Followed the quick start for creating a node.js app.
Ran the app. Ensured it worked by sending an e-mail.
Modified the test Checking that the sample can send an email to use my account parameters.
Tried to run the test. It fails with 403: insufficient scope. The call to get the token returned scopes, but lacked Mail.Send.
In the post data for the call to login.microsoftonline.com, I added "scope: 'Mail.Send'"
I still receive a valid token, and the return scope includes Mail.Send, but when I try to post with that token, I get 400: cannot POST /beta/me/sendMail
I tried adding scope (Mail.Send) in the query string and as a header (thought I saw that somewhere), but it made no difference.
I added the Mail.Send permission (under "Application Permissions") for the app in the application registration portal.
I compared the token (using https://jwt.ms) from my test call to the call from the app when it works. I see no real difference. They both contain the Mail.Send scope.
Here is the code (which is only slightly different from what's in the sample):
// in graphHelper.js
function postSendMail(accessToken, message, callback) {
request
.post('https://graph.microsoft.com/beta/me/sendMail')
//.post('https://graph.microsoft.com/beta/me/sendMail?scope=Mail.Send') // nope
.send(message)
.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/json')
.set('Content-Length', message.length)
.set('scope', 'Mail.Send') // nope
.end((err, res) => {
callback(err, res);
});
}
describe('Integration', function () { // mocha
var accessToken;
var scope;
const config = getConfig();
// My account variables in testConfig.json file
function getConfig() {
var configFilePath = path.join(__dirname, 'testConfig.json');
return JSON.parse(fs.readFileSync(configFilePath, { encoding: 'utf8' }));
}
function getAccessToken(done) {
var postData = querystring.stringify(
{
grant_type: 'password',
//grant_type: 'client_id', // not supported
//grant_type: 'authorization_code', // This assumes you've requested an auth code.
resource: 'https://graph.microsoft.com/',
scope: 'Mail.Send',
client_id: config.test_client_id_v2,
client_secret: config.test_client_secret_v2,
username: config.test_username,
password: config.test_password
}
);
var postOptions = {
host: 'login.microsoftonline.com',
port: 443,
path: '/common/oauth2/token',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
var postRequest = https.request(postOptions, function (res) {
var data = '';
res.setEncoding('utf8');
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
const response = JSON.parse(data);
accessToken = response.access_token;
scope = response.scope;
done();
});
});
postRequest.on('error', function (e) {
console.log('Error: ' + e.message);
done(e);
});
postRequest.write(postData);
postRequest.end();
}
before( // eslint-disable-line no-undef
function (done) {
getAccessToken(done);
}
);
it('Checking that the sample can send an email',
function (done) {
var postBody = emailer.generateMailBody(config.test_name, config.test_username);
graphHelper.postSendMail(
accessToken, scope,
JSON.stringify(postBody),
function (error) {
assert(error === null, `The sample failed to send an email: ${error}`);
done();
});
}
);
});

How to get updated session data on rails using AngularJs without page refresh

I'm currently working on integrating devise as an authentication backend with angular as its frontend.
I have faced a problem on when login and logout, the session data will be updated untill the page refresh.
What i will do get session data without page refresh..?
Thanks for your Answers...
AngularJs Controller :
function UsersCtrl($scope, Session) {"use strict";
$scope.CurrentUser = Session.requestCurrentUser();
$scope.login = function(user) {
$scope.authError = null;
Session.login(user.email, user.password)
.then(function(response) {
if (!response) {
$scope.authError = 'Credentials are not valid';
} else {
$scope.authError = 'Success!';
}
}, function(response) {
$scope.authError = 'Server offline, please try later';
});
};
$scope.logout = function() {
// alert("woow");
Session.logout();
};
$scope.register = function(user) {
$scope.authError = null;
console.log(user);
Session.register(user.email, user.password, user.confirm_password)
.then(function(response) {
}, function(response) {
var errors = '';
$.each(response.data.errors, function(index, value) {
errors += index.substr(0,1).toUpperCase()+index.substr(1) + ' ' + value + ''
});
$scope.authError = errors;
});
};
}
AngularJs Session Service:
angular.module('sessionService', ['ngResource'])
.factory('Session', function($location, $http, $q) {
// Redirect to the given url (defaults to '/')
function redirect(url) {
url = url || '/';
$location.path(url);
}
var service = {
login: function(email, password) {
return $http.post('/users/login', {user: {email: email, password: password} })
.then(function(response) {
service.currentUser = response.data.user;
if (service.isAuthenticated()) {
//$location.path(response.data.redirect);
$location.path('/store');
}
});
},
logout: function() {
$http.delete('/sessions').then(function(response) {
$http.defaults.headers.common['X-CSRF-Token'] = response.data.csrfToken;
service.currentUser = null;
redirect('/store');
});
},
register: function(email, password, confirm_password) {
return $http.post('/users', {user: {email: email, password: password, password_confirmation: confirm_password} })
.then(function(response) {
service.currentUser = response.data;
if (service.isAuthenticated()) {
console.log("authenticated");
$location.path('/');
}
});
},
requestCurrentUser: function() {
if (service.isAuthenticated()) {
return $q.when(service.currentUser);
} else {
return $http.get('/users').then(function(response) {
service.currentUser = response.data.user;
return service.currentUser;
});
}
},
currentUser: null,
isAuthenticated: function(){
return !!service.currentUser;
}
};
return service;
console.log(service);
});
One Thing about building applications like this (restful) is that understanding the the backend as an api and app as a front-end very well.
Then think about a story as such;
In the login screen of your app
Front-end: You Provided the credentials to your backend;
Back-end: Checked and authenticated then It will create a unique hash stored in db (JWT recommended to check expiration in frontend) to your Front-end.
Front-end:Save it in a cookie.
Also place it in your ajax setting header part as "Authorization: {token}"
Front-end: Then send each request with this header to your backend.
Back-end: Always check if the token is present and valid to provide resources.
http://www.thebuzzmedia.com/designing-a-secure-rest-api-without-oauth-authentication/ this link has helped me understand the whole thing and misconceptions in the past.
use $window.location.reload(); before page redirect.
One way to achieve this could be overriding the devise sessions_controller destroy action and afrer doing sign_out #current_user return the session as json

Resources