I've got a simple Backbone.js app which uses json-server as a backend. I have a function to log in, that finds a user from the collection, but I don't get how will I save my session. I thought about storing a parameter in cookies that later will be checked on every redirect. Here is my model:
var User = Backbone.Model.extend({
defaults: {
login: '',
password: '',
authToken: ''
}
});
And here is my collection:
var UserCollection = Backbone.Collection.extend({
url: 'http://localhost:3000/users',
// creates a random token
setToken: function () {
var rand = function () {
return Math.random().toString(36).substr(2)
}
var token = rand() + rand();
this.set({authToken: token});
}
});
And this is the view with login function
var LoginView = Backbone.View.extend({
initialize: function () {
this.collection = new UserCollection();
// template
}
// render function omitted
signIn: function () {
var login = $('#login').val();
var password = $('#password').val();
/**
finds a user within with the values from input fields
inside the collection
*/
if (login && password) {
this.collection.fetch({
data: {
login: login,
password: password
}
});
}
}
});
This function returns me an array with one object that is my requested model. All that I need is to use my setToken method and to save this model's authToken in cookie so that I could use it elsewhere in app, but I don't actually get how to do that.
Using a model to handle authentication would make more sense than a collection. Keep the model's responsibility simple and scoped to one thing. A model to handle the authentication, then a model to handle calls to other object which needs to be authenticated, not both at once.
I personally based authentication on Backbone-session's model.
// Using CommonJS
var Session = require('backbone-session');
// Extend from Session to implement your API's behaviour
var Account = Session.extend({
urlRoot: 'http://localhost:3000/users',
signIn: function(opt) {
opt = opt || {};
opt.data = _.extend({}, {
login: opt.login,
password: opt.password
}, opt.data);
return this.fetch(opt);
},
signOut: function(opt) { /** handle logout */ },
getAuthStatus: function() { /** handle refetching if needed */ }
});
Which I expose as a service to my application. In this session module, I override Backbone.Sync to ensure auth for each following calls to the API for any models or collection.
var mySession = new Account();
Backbone.sync = (function(syncFn) {
return function(method, model, options) {
options = options || {};
var beforeSend = options.beforeSend,
error = options.error;
// Add auth headers
options.beforeSend = function(xhr) {
xhr.setRequestHeader('Authorization', "Bearer " + mySession.get('authToken'));
if (beforeSend) return beforeSend.apply(this, arguments);
};
// handle unauthorized error (401)
options.error = function(xhr, textStatus, errorThrown) {
if (error) error.call(options.context, xhr, textStatus, errorThrown);
if (xhr.status === 401) {
mySession.signOut();
}
};
return syncFn.apply(this, arguments);
};
})(Backbone.sync);
Backbone-session's model uses the local storage as a backend. Its own sync method is overriden to use the local storage instead of the default sync behavior.
sync: function(method, model, options) {
options = options || {};
var url = model.options.url || model.url;
var key = _.isFunction(url) ? url() : '' + url;
var response;
switch (method) {
case 'create':
case 'update':
var data = model.toJSON();
var text = JSON.stringify(data);
response = localStorage.setItem(key, text);
break;
case 'delete':
response = localStorage.removeItem(key);
break;
case 'read':
response = JSON.parse(localStorage.getItem(key));
break;
}
if (_.isFunction(options.success)) {
options.success(response);
}
return Backbone.$.Deferred()
.resolve(response)
.promise();
},
Why the local storage?
You could use this implementation and change it minimally to use cookies instead.
The local storage was a better option for me since my API is on another domain and uses CORS to enable public access. Safari has limitation on cookies.
Safari also blocks cookies from sites that haven't been visited
directly. You can see in the security settings. It's default setting
is Accept cookies: "Only from sites I visit".
Related
I am trying to implement MSAL auth via Redirect in Vue js.
I have followed the official guide but when I open an app, handleRedirect is started, then it redirects to a blank page and hangs there, the console looks like this:
handleRedirectPromise called but there is no interaction in progress, returning null.
Emitting event: msal:acquireTokenStart
Emitting event: msal:handleRedirectEnd
Null, no response
Emitting event: msal:acquireTokenStart
I cannot get how to implement the redirect flow (popup flow is not an option).
My config:
// login
var msalConfig = {
auth: {
clientId: clientId,
authority: this.authority, //https://login.microsoftonline.com/common/ default
redirectUri: this.redirect + 'redirects/login-msal-powerbi/index.html', // blank page
postLogoutRedirectUri: null
},
cache: {
cacheLocation: "localStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
},
allowRedirectInIframe: true
}
My main LoginUser function:
// login
loginUser: function() {
var that = this;
// Prepare config
this.msalInstance = new msal.PublicClientApplication(msalConfig);
// Register Callbacks for Redirect flow
var request = this.msalGetRequestLoginRedirect(this.powerbi.scopes, this.login.aadTenant); // returns an object with authority and scopes
this.msalInstance.handleRedirectPromise()
.then(function(responce) {
that.msalHandleResponse(responce, that.msalInstance);
})
.catch((error) => {
console.log(error);
});
My msalHandleResponse function:
msalHandleResponse: function(response, msalInstance) {
if (response !== null) {
console.log(response);
}else {
console.error('null!!') // returns null
return msalInstance.loginRedirect({scopes:[
"https://analysis.windows.net/powerbi/api/Dashboard.Read.All",
"https://analysis.windows.net/powerbi/api/Dataset.Read.All",
"https://analysis.windows.net/powerbi/api/Report.Read.All",
"https://analysis.windows.net/powerbi/api/Group.Read.All",
"https://analysis.windows.net/powerbi/api/Workspace.Read.All",
"https://analysis.windows.net/powerbi/api/UserState.ReadWrite.All"
], responseMode:"query"});
}
},
My acquireTokenRedirect function:
msalGetTokenRedirect: function(msalInstance) {
var that = this;
msalInstance.acquireTokenSilent(this.requestsObj.silentRequest)
.then(function(tokenResponse) {
// Optionally make something with tokenResponse
console.log("Access token acquired silently...");
return tokenResponse;
}).catch(function(error) {
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return msalInstance.acquireTokenRedirect(this.requestsObj.request)
.then(function(tokenResponse) {
console.log('TOKEN RESPONSE',tokenResponse);
}).catch(function(error) {
console.error(error);
});
}
});
},
Are you calling handleRedirectPromise on your redirectUri (blank page)?
I'm trying to implement Google Fit, in a school project.
I am using the documentation released by Google but it does not work.
I am getting 403 bad request error
Is it possible because I am using XAMPP to try to connect, it gives me an error?
I have entered the request domains on the Google project.
Can you help me?
Also I have a question:
Does it cost to use Google Fit Api rest?
Thanks everyone for your answers
var CLIENT_SECRET;
var CLIENT_ID;
$.post('healthDataFunc.php', { functionname: 'get_app_credentials' }, function(data){
var result = JSON.parse(data);
if (result){
CLIENT_SECRET = result[0];
CLIENT_ID = result[1];
}
});
var CLIENT_REDIRECT = "https://localdefault.com:4433";
//End-Configuration
var SCOPES_FITNESS = 'https://www.googleapis.com/auth/fitness.activity.read+https://www.googleapis.com/auth/fitness.body.read+https://www.googleapis.com/auth/fitness.location.read+https://www.googleapis.com/auth/fitness.blood_pressure.read+https://www.googleapis.com/auth/fitness.sleep.read';
var GoogleAuth;
/*
Initial request for Google authentication code
Opens google auth window
returns Google Auth token
*/
function requestGoogleoAuthCode() {
// Load the API's client and auth2 modules.
// Call the initClient function after the modules load.
gapi.load('client:auth2', initClient);
console.log(gapi);
}
function initClient() {
// In practice, your app can retrieve one or more discovery documents.
var discoveryUrl = "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest";
// Initialize the gapi.client object, which app uses to make API requests.
// Get API key and client ID from API Console.
// 'scope' field specifies space-delimited list of access scopes.
gapi.client.init({
'apiKey': CLIENT_SECRET,
'clientId': CLIENT_ID,
'discoveryDocs': [discoveryUrl],
'scope': SCOPES_FITNESS
}).then(function () {
GoogleAuth = gapi.auth2.getAuthInstance();
// Listen for sign-in state changes.
GoogleAuth.isSignedIn.listen(updateSigninStatus);
// Handle initial sign-in state. (Determine if user is already signed in.)
var user = GoogleAuth.currentUser.get();
GoogleAuth.signIn();
console.log("test--------->", GoogleAuth, user);
});
}
/*
Uses Google Auth code to get Access Token and Refresh Token
returns object with access token, refresh token and access token expiration
*/
function getAccessToken(google_auth_code) {
var retVal = null;
jQuery.ajax({
url: "https://www.googleapis.com/oauth2/v3/token?code=" + google_auth_code + "&redirect_uri=" + CLIENT_REDIRECT + "&client_id=" + CLIENT_ID + "&client_secret=" + CLIENT_SECRET + "&scope=&grant_type=authorization_code",
type: "post",
success: function (result) {
console.log("Got Access Token And Refresh Token");
retVal = result;
console.log(result);
},
error: function (jqXHR, textStatus, errorThrown) {
console.log("Error during getAccessToken");
console.log(jqXHR);
console.log(textStatus);
console.log(errorThrown);
retVal = null;
},
async: false
});
return retVal;
}
/*
Uses Refresh token to obtain new access token
returns new access token with expiration
*/
function refreshAccessToken(refresh_token) {
var retVal = null;
jQuery.ajax({
url: "https://www.googleapis.com/oauth2/v3/token?client_secret=" + CLIENT_SECRET + "&grant_type=refresh_token&refresh_token=" + refresh_token + "&client_id=" + CLIENT_ID,
type: "post",
success: function (result) {
console.log("Refreshed Access Token");
retVal = result;
},
error: function (jqXHR, textStatus, errorThrown) {
console.log("Error during refreshAccessToken");
console.log(jqXHR);
console.log(textStatus);
console.log(errorThrown);
retVal = null;
},
async: false
});
return retVal;
}
function revokeAccess(accessToken) {
GoogleAuth.disconnect();
}
var isAuthorized;
var currentApiRequest;
/**
* Store the request details. Then check to determine whether the user
* has authorized the application.
* - If the user has granted access, make the API request.
* - If the user has not granted access, initiate the sign-in flow.
*/
function sendAuthorizedApiRequest(requestDetails) {
currentApiRequest = requestDetails;
if (isAuthorized) {
// Make API request
// gapi.client.request(requestDetails)
// Reset currentApiRequest variable.
currentApiRequest = {};
} else {
GoogleAuth.signIn();
}
}
/**
* Listener called when user completes auth flow. If the currentApiRequest
* variable is set, then the user was prompted to authorize the application
* before the request executed. In that case, proceed with that API request.
*/
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
isAuthorized = true;
if (currentApiRequest) {
sendAuthorizedApiRequest(currentApiRequest);
}
} else {
isAuthorized = false;
}
}
I am building a WebApp that includes heavy AJAX calling from the frontend and NodeJS Express at the backend. My Frontend Code looks like this-
This is the global AJAX function I am using in my all projects-
function _ajax(params = {}, next = null, url = '', method = 'post', carry = null) {
params = this._isObjectValid(params, 1, -1) ? params : {};
for (let key in params) {
if (params.hasOwnProperty(key)) {
const checkObject = params[key];
if (typeof checkObject === 'object' || Array.isArray(checkObject)) {
params[key] = JSON.stringify(checkObject);
}
}
}
const httpRequest = new XMLHttpRequest();
httpRequest.open(method, url, true);
httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
const p = Object.keys(params).map(function (value) {
return encodeURIComponent(value) + '=' + encodeURIComponent(params[value]);
}).join('&');
if (next) {
httpRequest.onreadystatechange = function () {
next(httpRequest, carry);
};
}
httpRequest.send(p);
}
This is the global Click Event Binding function
function _click(next, target, node, extra = null) {
node.onclick = (event) => {
next(event, target, extra);
};
return node;
},
This is my AJAX Request
_click(
() => {
_ajax(
{mod: 'login'},
(request) => {
console.info(request.status);
console.info(request.response);
},
'http://localhost:1008/'
)
}, null, buttonSubmit);
My Backend Code for Handling Post AJAX Requests is:
app.post('*', async (req, res) => {
console.info('POST RECEIVED');
const params = req.body;
console.info(params);
await posts(req, res, params, dbo);
});
export async function posts(req, res, params, dbo) {
if (res) {
const mod = params.mod;
switch (mod) {
case 'user':
await sendPass(res, 'User Will', null);
break;
default:
await send(res, 'Invalid Module Call', null);
}
}
}
export function send(res, message, data, result = false) {
res.send({result: result, message: message, data: data});
res.end();
}
export function sendError(res, message, data) {
send(res, message, data, false);
}
export function sendPass(res, message, data) {
send(res, message, data, true);
}
Now in any other server like PHP or .NET, my web app is getting exactly one response from the server when I click the button, but for NodeJS I am getting three responses while I am processing the AJAX request only once-
This is repeating for every AJAX Request. So If I'm processing 3 AJAX Requests, my Web App is receiving 9 responses. I tried to search on the internet on this but can't find much. Since this scenario is not repeating on any other Server except NodeJs, so it may be not a problem from JavaScript event binding or ajax processing, or a browser issue.
Any suggestion will be appreciable. Thanks in advance.
I found the bug in the global AJAX function. I need to detect the status of readyState of httpRequest. I'm posting an updated version of the function:
Before:
httpRequest.onreadystatechange = function () {
next(httpRequest, carry);
};
After:
/**
* readyState values
* 0: request not initialized
* 1: server connection established
* 2: request received
* 3: processing request
* 4: request finished and response is ready
*/
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState === 4)
next(httpRequest, carry);
};
Surprisingly, it was not creating problems in other servers like WAMP or .NET for reasons I still don't know.
I am releasing access to pages using connect-roles and loopback but I have a pertinent question about how I can collect the customer's role and through the connect-roles to read the session and respond to a route.
Example, when the client logs in I load a string containing the client's role and access it in a function that controls access to pages.
I have this doubt because I'm finalizing a large scale service that usually there are multiple client sessions that are accessed instantly using a same storage and check function.
It would be efficient to store the customer's role using app.set() and app.get()?
app.get('/session-details', function (req, res) {
var AccessToken = app.models.AccessToken;
AccessToken.findForRequest(req, {}, function (aux, accesstoken) {
// console.log(aux, accesstoken);
if (accesstoken == undefined) {
res.status(401);
res.send({
'Error': 'Unauthorized',
'Message': 'You need to be authenticated to access this endpoint'
});
} else {
var UserModel = app.models.user;
UserModel.findById(accesstoken.userId, function (err, user) {
// console.log(user);
res.status(200);
res.json(user);
// storage employee role
app.set('employeeRole', user.accessLevel);
});
}
});
});
Until that moment everything happens as desired I collect the string loaded with the role of the client and soon after I create a connect-roles function to validate all this.
var dsConfig = require('../datasources.json');
var path = require('path');
module.exports = function (app) {
var User = app.models.user;
var ConnectRoles = require('connect-roles');
const employeeFunction = 'Developer';
var user = new ConnectRoles({
failureHandler: function (req, res, action) {
// optional function to customise code that runs when
// user fails authorisation
var accept = req.headers.accept || '';
res.status(403);
if (~accept.indexOf('ejs')) {
res.send('Access Denied - You don\'t have permission to: ' + action);
} else {
res.render('access-denied', {action: action});
// here
console.log(app.get('employeeRole'));
}
}
});
user.use('authorize access private page', function (req) {
if (employeeFunction === 'Manager') {
return true;
}
});
app.get('/private/page', user.can('authorize access private page'), function (req, res) {
res.render('channel-new');
});
app.use(user.middleware());
};
Look especially at this moment, when I use the
console.log(app.get('employeeRole')); will not I have problems with simultaneous connections?
app.get('/private/page', user.can('authorize access private page'), function (req, res) {
res.render('channel-new');
});
Example client x and y connect at the same time and use the same function to store data about your session?
Being more specific when I print the string in the console.log(app.get('employeeRole')); if correct my doubt, that I have no problem with simultaneous connections I will load a new variable var employeeFunction = app.get('employeeRole'); so yes my function can use the object containing the role of my client in if (employeeFunction === 'Any Role') if the role that is loaded in the string contain the required role the route it frees the page otherwise it uses the callback of failureHandler.
My test environment is limited to this type of test so I hope you help me on this xD
Instead of using app.set you can create a session map(like hashmaps). I have integrated the same in one of my projects and it is working flawlessly. Below is the code for it and how you can access it:
hashmap.js
var hashmapSession = {};
exports.auth = auth = {
set : function(key, value){
hashmapSession[key] = value;
},
get : function(key){
return hashmapSession[key];
},
delete : function(key){
delete hashmapSession[key];
},
all : function(){
return hashmapSession;
}
};
app.js
var hashmap = require('./hashmap');
var testObj = { id : 1, name : "john doe" };
hashmap.auth.set('employeeRole', testObj);
hashmap.auth.get('employeeRole');
hashmap.auth.all();
hashmap.auth.delete('employeeRole');
I'm trying to understand the flow how to authenticate user on WEB client (JS), and then use Google API on my back-end server (ASP.NET MVC application), on behalf of authenticated user for retrieving users contacts list.
Here the current flow that I use:
1.In HTML I use google JS client: https://apis.google.com/js/client.js:
function auth(callback) {
var config = {
'client_id': '***********',
'scope': 'https://www.googleapis.com/auth/contacts.readonly'
};
config.immediate = true;
gapi.auth.authorize(config, function (authResult) {
if (authResult && !authResult.error) {
callback();
}
else {
config.immediate = false;
gapi.auth.authorize(config, function (response) {
//Here I send access_token to back-end using HTTPS
});
}
});
}
2.Then I use gapi.auth.getToken() and send it to back-end server (Using a HTTPS AJAX call)
3.Then on server I have the following code in controller:
public JsonResult Get(TokenModel model)
{
//Custom store for access_token
var myStore = new MyStore(NewtonsoftJsonSerializer.Instance.Serialize(new TokenResponse() { Issued = DateTime.Now, ExpiresInSeconds = 3600, TokenType = "Bearer", AccessToken = model.access_token }));
string[] Scopes = { PeopleService.Scope.ContactsReadonly };
ClientSecrets secrets = new ClientSecrets() { ClientId = "******", ClientSecret = "******" };
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
secrets,
Scopes,
"user",
CancellationToken.None,
myStore
).Result;
var service = new PeopleService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
List<string> result = GetPeople(service, null);
return Json(result);
}
Questions:
Is it the correct flow and does GoogleWebAuthorizationBroker is a correct class to use on server in my case?
Why and HOW GoogleWebAuthorizationBroker opens a new browser window for authentication, in case model.access_token = null?
Why when the token is not valid (ex: “dasdasdasdas”), AuthorizeAsync method returns me the UserCredential that looks absolutely valid, but then the exception occurs when make actual request to google api.
How from the above flow, I can get “refresh token” for later use (as I understand, I need somehow generate it myself, using access_token + secret key).
Thanks!