Hapi jwt extra auth check on owner id - hapi.js

I'm working on a project
using hapi-auth-jwt.
So when the user is logged
get this token
exports.getUserToken = function getUserToken(user) {
var userData = {
username: user.username,
scope: ['user'],
iss: apiConfig.iis,
jti: user.id
};
var token = {
token : Jwt.sign(userData, apiConfig.secret, { expiresInMinutes: apiConfig.expiresInMinutes})
};
return token;
};
and in the route
{
method: 'GET',
path: internals.resourcePath + '/{userId}',
config : {
handler : User.findById,
validate: Validator.findById,
auth: {
strategy: 'token',
scope: ['user']
}
}
},
but I've to do an other check like
(in controller for get one ,update and delete )
to see if the user is really the owner
var jti = request.auth.credentials.jti;
var id = +request.params.userId;
if( id !== jti ){
return reply( ReplyUtil.forbidden() );
}
var params =request.payload;
User.findOne
I'm wondering if there is a way to not to have
my code duplicated (sort of middleware in express)
Is it worth using a pre-hook like
server.ext('onPostAuth' , function(request, reply) {
if(request.auth && request.auth.credentials && request.auth.credentials.jti){
var jti = request.auth.credentials.jti;
var id = +request.params.userId;
if( id !== jti ){
return reply(Boom.forbidden());
}
}
return reply.continue();
});

First, in your validateFunc, add user's scope his/her id:
user.scope.push(user._id);
When you call callback function in validateFunc, don't forget to pass the user object as credentials. Then in your route, define scope something like this:
auth: {
strategy: 'token',
scope: ['admin', 'USER_ID']
}
Hope it helps.

Related

Do I still need users in user pool if I want to work Custom Authentication workflow in AWS Cognito?

I have a webservice which validates user/pwd and returns true/false (valid/invalid). I am trying to leverage Custom Authentication Workflow of AWS Cognito to integrate with the webservice.
I read through the docs and came across the define, create and verify lambda triggers and I tried those as follows:
Define trigger:
exports.handler = async (event) => {
if (!event.request.session || event.request.session.length === 0) {
event.response.challengeName = "CUSTOM_CHALLENGE";
event.response.failAuthentication = false;
event.response.issueTokens = false;
} else if (event.request.session.length === 1) {
// If we passed the CUSTOM_CHALLENGE then issue token
event.response.failAuthentication = false;
event.response.issueTokens = true;
} else {
// Something is wrong. Fail authentication
event.response.failAuthentication = true;
event.response.issueTokens = false;
}
return event;
};;
Create Trigger:
exports.handler = async (event) => {
if (event.request.challengeName == 'CUSTOM_CHALLENGE') {
event.response.publicChallengeParameters = {};
event.response.privateChallengeParameters = {};
}
return event;
}
Verify Trigger:
exports.handler = async (event, context) => {
//call webservice using "event.userName" and "event.request.challengeAnswer" (password)
var result = <bool-result-received-from-webservice>
event.response.answerCorrect = result;
return event;
};
The JS client looks like this:
Amplify.configure({
Auth: {
region: 'dd',
userPoolId: 'eeee',
userPoolWebClientId: 'ffff',
authenticationFlowType: 'CUSTOM_AUTH'
}
})
let user = await Auth.signIn(username)
.then(u => {
console.log(u); //(1) TOKENS ARE ALREADY CREATED HERE WITHOUT VERIFYING PASSWORD. NOT SURE.
if (u.challengeName === 'CUSTOM_CHALLENGE') {
console.log("responding to challenge..");
// to send the answer of the custom challenge
Auth.sendCustomChallengeAnswer(u, password)
.then(u2 => {
console.log("after responding to challenge...");
console.log(u2); //(2) NEW TOKENS ARE CREATED HERE. NOT SURE.
return u2;
})
.catch(err => {
console.log("ERROR with Challenge:");
console.log(err);
});
} else {
console.log("no challenge needed..");
return u;
}
})
.catch(err => {
console.log("ERROR with sign-in:..");
console.log(err);
});
I mentioned 1 and 2 in the comments above. Not sure if it's behaving correctly.
If the username is not in the "users" list of "user pool", it throws it as invalid login. Is it possible to validate username/password only through webservice having no "users" in the "user pool"?

Migrate ADAL.js to MSAL.js

I have a SPA which uses the solution provided here to authenticate with Azure AD and everything works as expected. Now I want to migrate this to use MSAL.js.
I use below for login:
import * as MSAL from 'msal'
...
const config = {
auth: {
tenantId: '<mytenant>.com',
clientId: '<myclientid>',
redirectUri: <redirecturi>,
},
cache: {
cacheLocation: 'localStorage',
}
};
const tokenRequest = {
scopes: ["User.Read"]
};
export default {
userAgentApplication: null,
/**
* #return {Promise}
*/
initialize() {
let redirectUri = config.auth.redirectUri;
// create UserAgentApplication instance
this.userAgentApplication = new MSAL.UserAgentApplication(
config.auth.clientId,
'',
() => {
// callback for login redirect
},
{
redirectUri
}
);
// return promise
return new Promise((resolve, reject) => {
if (this.userAgentApplication.isCallback(window.location.hash) || window.self !== window.top) {
// redirect to the location specified in the url params.
}
else {
// try pull the user out of local storage
let user = this.userAgentApplication.getUser();
if (user) {
resolve();
}
else {
// no user at all - go sign in.
this.signIn();
}
}
});
},
signIn() {
this.userAgentApplication.loginRedirect(tokenRequest.scopes);
},
And then I use below to get the token:
getCachedToken() {
var token = this.userAgentApplication.acquireTokenSilent(tokenRequest.scopes);
return token;
}
isAuthenticated() {
// getCachedToken will only return a valid, non-expired token.
var user = this.userAgentApplication.getUser();
if (user) {
// get token
this.getCachedToken()
.then(token => {
axios.defaults.headers.common["Authorization"] = "Bearer " + token;
// get current user email
axios
.get('<azureapi-endpoint>' + '/GetCurrentUserEmail')
.then(response => { })
.catch(err => { })
.finally(() => {
});
})
.catch(err => { })
.finally(() => { });
return true;
}
else {
return false;
}
},
}
but after login I get below error:
Access to XMLHttpRequest at 'https://login.windows.net/common/oauth2/authorize?response_type=code+id_token&redirect_uri=<encoded-stuff>' (redirected from '<my-azure-api-endpoint>') from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Also the token that I get seems to be invalid as I get 401 errors trying to call api using the token. Upon checking the token against https://jwt.io/ I get an invalid signature.
I really appreciate anyone's input as I've already spent good few days and haven't got anywhere yet.
I'm not sure if this is your issue. however, for msal.js, in the config, there is no tenantId parameter, it's supposed to be authority. Here is a sample for graph api using msal.js
https://github.com/Azure-Samples/active-directory-javascript-graphapi-v2
specifically: the config is here: https://github.com/Azure-Samples/active-directory-javascript-graphapi-v2/blob/quickstart/JavaScriptSPA/authConfig.js
as per here, https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-js-initializing-client-applications it is supposed to be hitting login.microsoftonline.com not login.windows.net

serverless custom authorizer denying authorization

I'm building an api with serverless and a custom authorizer on api-gateway. When running serverless-local and passing a valid authorization token, I'm getting 401 responses.
I've mostly copied this from examples, the function which is called by the authorizer is
var apiGatewayAuth = function(event, context, callback) {
if(event.authorizationToken == 'undefined'){
return callback(null,generatePolicy('', 'Deny', event.methodArn));
}
var token = validateToken(event.authorizationToken);
if (token.error) {
return callback(null, generatePolicy('', 'Deny', event.methodArn));
}
console.log('allow',generatePolicy(token.sub, 'Allow', event.methodArn))
return callback(null,generatePolicy(token.sub, 'Allow', event.methodArn));
};
The policy is generated via
var generatePolicy = function(accountId, effect, resource) {
var authResponse = {};
authResponse.principalId = accountId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
return authResponse;
};
The returned policy looks like
{ principalId: 'test_api_user',
policyDocument: {
Version: '2012-10-17',
Statement: [ { Action: 'execute-api:Invoke',
Effect: 'Allow',
Resource: 'my:arn' } ]
}
Is there something I am obviously doing wrong here?
My token.sub is a string of unique user name for my app, but AWS has no concept of who the user is, I thought this was fine.

Twitter OAuth Ionic 2

Its possible generate a Twitter token and secret token in Nodejs and after use it to open the browser for authenticate with "https://api.twitter.com/oauth/authenticate"?
I use this way to get the token:
app.get('/auth/twitter/token', function (req, res) {
var requestTokenUrl = 'https://api.twitter.com/oauth/request_token';
var requestTokenOauth = {
consumer_key: "2z8MTR8KAZuFafPHsEQ0ZBgo1",
consumer_secret: "ksPiaQz7ihCrOh3m4iRCsXZzQuSkkmcv4CLGiJQwREWeaQl7St"
};
request.post({
url: requestTokenUrl,
oauth: requestTokenOauth
}, function (err, response, body) {
var oauthToken = qs.parse(body);
res.send(oauthToken);
});
});
When I get this token in the client "https://api.twitter.com/oauth/authenticate?oauth_token=TOKEN" I got this problem: "This page is no longer valid. It's looks like someone already used the token information your provider, blabla.."
The problem is due to the way that I get the Token?
I'm using ng2-cordova-auth but this lib dont have twitter auth, I'm just trying to implement
This is my implementation:
"use strict";
var utility_1 = require("../utility");
var PROVIDER_NAME = "Twitter";
var Twitter = (function () {
function Twitter(options) {
this.twitterOptions = options;
this.flowUrl = ""
}
Twitter.prototype.login = function (token, tokenSecret) {
var _this = this;
return new Promise(function (resolve, reject) {
_ this.flowUrl = "https://api.twitter.com/oauth/authenticate?oauth_token="+token;
var browserRef = window.cordova.InAppBrowser.open(_this.flowUrl);
browserRef.addEventListener("loadstart", function (event) {
if ((event.url).indexOf(_this.twitterOptions.redirectUri) === 0) {
browserRef.removeEventListener("exit", function (event) { });
browserRef.close();
var parsedResponse = event.url.split("?")[1].split("&");
if (parsedResponse) {
resolve(parsedResponse);
}
else {
reject("Problem authenticating with " + PROVIDER_NAME);
}
}
});
browserRef.addEventListener("exit", function (event) {
reject("The " + PROVIDER_NAME + " sign in flow was canceled");
});
});
};
return Twitter;
}());
exports.Twitter = Twitter;
In my component/controller I make this:
//With twitterToken I get the token from NodeJs
this.API.twitterToken().subscribe(
data => {
this.twitterOAuth.login(data.oauth_token, data.oauth_token_secret).then((success) => {
alert(JSON.stringify(success))
}, (error) => {
alert(JSON.stringify(error));
});
},
err => alert(JSON.stringify(err))
);
Have you tried the Twitter Connect plugin? Does this help?
Plugin to use Twitter Single Sign On Uses Twitter's Fabric SDK
An example of use is
import {TwitterConnect} from 'ionic-native';
function onSuccess(response) {
console.log(response);
// Will console log something like:
// {
// userName: 'myuser',
// userId: '12358102',
// secret: 'tokenSecret'
// token: 'accessTokenHere'
// }
}
TwitterConnect.login().then(onSuccess, onError);

Check user loginned on UI-Router

I have 1 variable to save data user when they logged in.
Var : isLoginned
When user logged in, i set isLoginned = '1' and save to LocalStorage.
I want to use isLoginned to check when router changes.
Check access to this router when user logged in Only. If not logged in, redirect to page sign in.
This is example code in Ui router
.state('access.page', {
url: '/page',
templateUrl: 'app/Components/Page/Page.html',
onEnter: function ($storage.isLoginned) {
if ($storage.isLoginned != '1') {
$location.path('/access/signin');
}
},
controller: function ($scope) {
$scope.page.setTitle('Page title');
}
})
User Service
/**
* Created by tatda_000 on 11/26/2014.
*/
'use strict';
app.factory('signinService', function ($http) {
return {
signin: function (user, scope) {
//console.log('Test function');
var $promise = $http.post('api/AccountController/Login', user); //post data to server
$promise.then(function (msg) {
//scope.msgtxt = msg.data;
if (msg.data.Message != 'Success') scope.msgtxt = 'Tên tài khoản hoặc mật khẩu không đúng!';
else scope.msgtxt = 'Success';
//save user infomation to Local Storage
scope.$storage.username = msg.data.Data.Username;
scope.$storage.password = msg.data.Data.Password;
scope.$storage.userToken = msg.data.Data.UserToken;
scope.$storage.isLoginned = '1';
});
}
}
});
User controller
'use strict';
/* Controllers */
// signin controller
app.controller('SigninController', ['$scope', '$localStorage', 'signinService', function ($scope, $localStorage, signinService) {
//set page title
$scope.page.setTitle('Đăng nhập');
$scope.login = function (user) {
signinService.signin(user, $scope); //call function login service
};
//create first value storage
$scope.$storage = $localStorage.$default({
username: "",
userToken: ''
});
$scope.logout = function () {
delete $scope.$storage.username;
delete $scope.$storage.userToken;
};
}]);