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.
Related
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"?
I'm fairly new to token based authentication and I have a problem of how to maintain login state after I login.
I want to create a SPA website for which I am using Knockoutjs for my front end and SammyJS for routing and changing the views.
After I login in and get the token I store it in localStorage and set the username into an observable which I am displaying.
My problem is that after I close the tab or browser and I go back to the site, the token is in the localStorage but I can't see the user logged in.
I want to maintain the login state until the token expires. My question is what should I do with the token from the localStorage when I enter the site in order to maintain the login state of that user?
Do I need to make something in the startup class or to check if that user exists in the DB?
Thanks in advance!
Here is my code:
StartupAuth.cs
[assembly: OwinStartup(typeof(EventHub.PL.WebUI.Startup))] namespace EventHub.PL.WebUI {
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get;private set; }
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public const string TokenEndpointPath = "/api/token";
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString(TokenEndpointPath),
Provider = new ApplicationOAuthProvider(PublicClientId),
//AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
//app.UseOAuthBearerTokens( OAuthOptions );
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
}
}
AccountController.cs
[HttpPost]
[AllowAnonymous]
[Route("Login")]
public async Task<IHttpActionResult> Login(LoginUser model)
{
var request = HttpContext.Current.Request;
var tokenServiceUrl = request.Url.GetLeftPart(UriPartial.Authority) + request.ApplicationPath + "/api/Token";
using (var client = new HttpClient())
{
var requestParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", model.Email),
new KeyValuePair<string, string>("password", model.Password)
};
var requestParamsFormUrlEncoded = new FormUrlEncodedContent(requestParams);
var tokenServiceResponse = await client.PostAsync(tokenServiceUrl, requestParamsFormUrlEncoded);
var responseString = await tokenServiceResponse.Content.ReadAsStringAsync();
var json = JsonConvert.DeserializeObject<TokenResponse>(responseString);
var responseCode = tokenServiceResponse.StatusCode;
if (responseCode == HttpStatusCode.OK)
{
RegisterUser user = userRepository.GetNameById(json.Id);
var data = new
{
status = "success",
json.access_token,
user.Lastname
};
return Json(data);
}
return Json(new { status = "failed" });
}
}
here is the KO part:
var LoginApp = function () {
var instance = this;
instance.mainViewModel = new MainViewModel();
instance.loginViewModel = new LoginViewModel();
instance.loginRepository = new LoginRepository();
instance.loginViewModel.signIn = function() {
$('.loader-header').show();
var postData = {
email: instance.loginViewModel.email(),
password: instance.loginViewModel.password
}
instance.loginRepository.SignIn(SignInSuccess, postData);
};
instance.SignInSuccess = function(response) {
if (response.status === 'success') {
instance.mainViewModel.username(response.Lastname);
instance.mainViewModel.isVisible(true);
var userData = {
token: response.access_token,
username: response.Lastname
};
localStorage.setItem('AuthorizationData', JSON.stringify(userData));
$('.loader-header').hide();
dialog.close();
} else {
$('.loader-header').hide();
}
};
instance.init = function () {
ko.applyBindings(instance.loginViewModel, document.getElementById("signin-form"));
ko.applyBindings(instance.mainViewModel, document.getElementById("main-wrapper"));
}
instance.init();
}
$(document).ready(function () {
var loginApp = LoginApp();
});
UPDATE
here is my routing also
var appRoot = root;
(function ($) {
var app = $.sammy('#page', function () {
this.get('#/home', function (context) {
document.title = 'Home - ' + title;
var url = getUrlFromHash(context.path);
loadView(url, new MainViewModel(), MainApp);
//context.load(url).swap();
});
this.get('#/about', function (context) {
var url = getUrlFromHash(context.path);
loadView(url, new AboutViewModel(), AboutApp);
});
this.get('#/manage', function (context) {
var url = getUrlFromHash(context.path);
loadView(url, new AboutViewModel(), AboutApp);
});
});
$(function () {
app.run('#/home');
});
})(jQuery);
function loadView(url, viewModel, callback) {
$.get(url, function (response) {
var $container = $('#page');
//var $view = $('#page').html(response);
$container.html(response);
callback();
});
}
function getUrlFromHash(hash) {
var url = hash.replace('#/', '');
if (url === appRoot)
url = 'home';
return url;
}
Right now all you're doing is storing the user's credentials in localStorage but not using them to perform authorization. One alternative is to use the Sammy.OAuth2 plugin (which you can find it here).
You can define a route to make the authentication like:
app.post("#/oauth/login", function(context) {
this.load('http://yourwebsite/login',
{
cache: false,
type: 'post',
data: {
email: $("input[name=email]").val(),
password: $("input[name=password]").val()
}
})
.then(function(content) {
if(content != false){
if(app.getAccessToken() == null){
app.setAccessToken(token());
}
}else{
app.trigger("oauth.denied");
return false;
}
});
});
In 'protected' routes you can check if the user is already logged in like this:
app.get("#/profile", function(context) {
if(app.getAccessToken() != null)
context.render('view/profile.template');
else
this.requireOAuth();
});
This examples will have to be modified to populate the token according to your scenario. Here's a complete tutorial on Sammy.Oath2.
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);
I've created an application using google drive API to list and manage all my drive files.
Everything goes fine, except the log out part. I've searched for two days for a solution without a result.
The following is code related to login and works fine:
function checkAuth() {
gapi.auth.authorize(
{
'client_id': CLIENT_ID,
'scope': SCOPES.join(' '),
'immediate': true,
'authuser': '-1'
}, handleAuthResult);
}
function handleAuthResult(authResult) {
var authorizeDiv = document.getElementById('authorize-div');
if (authResult && !authResult.error) {
// Hide auth UI, then load client library.
authorizeDiv.style.display = 'none';
loadDriveApi();
} else {
authorizeDiv.style.display = 'inline';
}
}
function handleAuthClick(event) {
gapi.auth.authorize(
{client_id: CLIENT_ID, scope: SCOPES, immediate: false},
handleAuthResult);
return false;
}
function loadDriveApi() {
gapi.client.load('drive', 'v2', listFiles);
}
I'm able to login and work with files, but when I try to logout with following I get No 'Access-Control-Allow-Origin' error
$(document).on('click', '.logout', function(){
var token = gapi.auth.getToken();
if (token) {
var accessToken = gapi.auth.getToken().access_token;
if (accessToken) {
var revokeToken = 'https://accounts.google.com/o/oauth2/revoke?token=' + accessToken;
jQuery.getJSON(revokeToken).success(function(data) {
console.log(data);
}).error(function(message) {
console.error('error' + message);
}).complete(function() {
console.log('completed!');
});
}
}
gapi.auth.setToken(null);
gapi.auth.signOut();
});
In Google Developers Console I've regitred my website to Authorized JavaScript origins.
Thanks
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.