Chrome extensions: issue with identity.launchWebAuthFlow - authentication

I'm tring to login via my own service. This is what I have now:
manifest.json
"background": {
"scripts": ["background.js"]
}
background.js
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.create({
url: 'index.html'
});
});
index.html is where all the extension's logic resides. Here I have a function that starts authentication process:
function goLogin(callback)
{
var redirectUrl = chrome.identity.getRedirectURL('receiveToken');
chrome.identity.launchWebAuthFlow({
url: 'http://todolist.dev/app_dev.php/login?response_type=token&redirect_url=' + redirectUrl,
interactive: true
}, function(redirectUrl) {
if (!redirectUrl) {
return;
}
// Get an access token from the url and save it in localStorage
var queryString = decodeURIComponent(redirectUrl.substr(redirectUrl.indexOf('?') + 1));
var params = queryString.split('&');
var accessToken = null;
for (var i = 0; i < params.length; i++) {
params[i] = params[i].split('=');
if (params[i][0] == 'access_token') {
accessToken = params[i][1];
break;
}
}
localStorage.setItem('accessToken', accessToken);
callback();
});
}
The problem is that the popup with the service's login page sometimes doesn't open or opens and closes automatically with the response that the user didn't approve access. Sometimes when the popup opens and I try to login with wrong credentials several times, the popup closes automatically as well (with the same "you didn't approve access" response). In the backend I don't have any restrictions to a number of login attempts.
In the backend I have a FOSUserBundle with overridden AuthenticationSuccessHandler (it does what the default success handler does + returns an access token).

Related

MSALJS version 1.2.1 loginRedirect does not invoke handleRedirectCallback when site is deployed

I am trying to get loginRedirect working with a React app. It works when running locally, but when I deploy to Azure App Service, then the redirect does not invoke handleRedirectCallback(), and the hash stays in the Url.
So I have kept reducing this until now I have just an index.html, which I have taken from here:
https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp/blob/master/index.html
This is from Micrososft's ADB2C With MSAL and SPA.
I changed the clientId and authority, provided a handleRedirectCallback and redirectUri, and changed myMSALObj.loginPopup() to myMSALObj.loginRedirect().
When I run locally, it works, although it executes 2 times (breakpoint on new UserAgentApplication(config)).
PROBLEM: When I deploy this simple page to Azure App Service, and click Login, the call to
myMSALObj.loginRedirect()
does navigate to the Microsoft login, but when it redirects back to my site and executes
new UserAgentApplication(config)
it does not fire handleRedirectCallback(), and does not consume the window.location.hash. The hash stays on the Url, and processing just stops.
I can set breakpoints in the deployed source (in Chrome Dev) and observe that after redirecting back to my page, it is definitely newing UserAgentApplication, but it never comes back from the constructor.
Here's the essential code from index.html, not showing the part that calls an Api etc:
<script>
"use strict";
// configuration to initialize msal
const msalConfig = {
auth: {
clientId: "xxxxx-xxxx-xxxx-xxxx-xxxxxxxx", //This is your client ID
authority: "https://xxxxxxxxx.b2clogin.com/xxxxxxxxx.onmicrosoft.com/B2C_1A_signup_signin",
validateAuthority: false,
redirectUri: 'https://xxxxxxxxx-react.azurewebsites.net'
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
// instantiate MSAL
const myMSALObj = new Msal.UserAgentApplication(msalConfig);
myMSALObj.handleRedirectCallback((err, response) => {
if (err) {
alert(err);
} else {
updateUI();
}
});
const loginRequest = {
scopes: appConfig.b2cScopes
};
function signIn() {
// myMSALObj.loginPopup(loginRequest).then(function (loginResponse) {
// getToken(tokenRequest).then(updateUI);
// }).catch(function (error) {
// logMessage(error);
// });
myMSALObj.loginRedirect(loginRequest)
}
function updateUI() {
console.log('inside updateUI');
const userName = myMSALObj.getAccount().name;
console.log(myMSALObj.getAccount());
console.log('username=' + userName);
logMessage("User '" + userName + "' logged-in");
}
</script>
The Azure App Service is Linux running PHP 7.3.

adal.js inifnite loop when refreshing token

I am using the latest adal.js to query data from MicroSoft Dynamics CRM. The code gets into an infinite loop when renewing the token.
Additionally after loging into microsoft and being redirected back to my page the adaljs tries to refresh the token.
Note - this is javascript in an ASP.NET MVC web app. It is not using angular js.
This is also similar to the SO question Adal & Adal-Angular - refresh token infinite loop
var endpoints = {
orgUri: "https://<tenant>.crm6.dynamics.com/"
};
var config = {
clientId: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
tenant: '<tenant>.onmicrosoft.com',
redirectUri: 'http://localhost:53290/home/AuthenticatedByCrm/',
endpoints: endpoints,
cacheLocation: 'localStorage'
};
var x = new AuthenticationContext(config);
var isCallback = x.isCallback(window.location.hash);
if (isCallback) {
x.handleWindowCallback();
x.acquireToken(endpoints.orgUri, retrieveAccounts);
} else {
x.login();
}
function retrieveAccounts(error, token) {
// Handle ADAL Errors.
if (error || !token) {
alert('ADAL error occurred: ' + error);
return;
}
var req = new XMLHttpRequest();
req.open("GET", encodeURI(organizationURI + "/api/data/v8.0/accounts?$select=name,address1_city&$top=10"), true);
//Set Bearer token
req.setRequestHeader("Authorization", "Bearer " + token);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
if (this.readyState == 4 /* complete */) {
req.onreadystatechange = null;
if (this.status == 200) {
var accounts = JSON.parse(this.response).value;
//renderAccounts(accounts);
}
else {
var error = JSON.parse(this.response).error;
console.log(error.message);
//errorMessage.textContent = error.message;
}
}
};
req.send();
}
The Active Directory Authentication Library (ADAL) for JavaScript helps you to use Azure AD for handling authentication in your single page applications. This library is optimized for working together with AngularJS.
Based on the investigation, this issue is caused by the handleWindowCallback. The response not able to run into the branch for if ((requestInfo.requestType === this.REQUEST_TYPE.RENEW_TOKEN) && window.parent && (window.parent !== window)) since it is not used in the Angular enviroment.
To integrate Azure AD with MVC application, I suggest that you using the Active Directory Authentication Library. And you can refer the code sample here.
Update
if (isCallback) {
// x.handleWindowCallback();
var requestInfo=x.getRequestInfo(window.location.hash);
//get the token provided resource. to get the id_token, we need to pass the client id
var token = x.getCachedToken("{clientId}")
x.saveTokenFromHash(requestInfo);
} else {
x.login();
}

How can I have a seperate login page using Durandal that has a different layout then the shell?

I've read through Durandal login page redirect pattern wow, lots of code to do what I'd think would be pretty simple.
I've also read through https://groups.google.com/forum/#!topic/durandaljs/RdGpwIm1oOU as I'd like the login page to have a simple logo with a login form, but I'd also like routing for a registration and about page as well. The rest of my site will have a menu, header, etc which I don't want to show until the user is logged in. Also, I'm not sure how this approach would update when the user logs in.
Another code example that almost does what I want to do: https://github.com/Useful-Software-Solutions-Ltd/Durandal451/blob/master/Durandal451v2/App/global/session.js
So, what should I do? Is there an official way to do this? There seems to be a mish mash of things out there that people have tried. I would think this would be a really common occurrence but couldn't find anything on the main docs.
I'm not sure this is the simplest way, but this is what I got
you will need to add some extra function after app.start() is triggered.
main.js
var auth = require('authentication'); // Authentication module
app.start().then(function()
{
// This function will wait for the promise
auth.init().then(function(data)
{
// When successfully authenticate, set the root to shell
app.setRoot('views/shell');
}
});
authentication.js
define(function(require)
{
var app = require('durandal/app');
return {
init: function()
{
// Initialize authentication...
return system.defer(function(dfd)
{
// Check if user is authenticate or if there has stored token
var isAuthenticate = someOtherFunctiontoCheck();
if (isAuthenticate)
{
dfd.resolve(true); // return promise
}
else
{
// When not authenticate, set root to login page
app.setRoot('views/login');
}
}
}
};
});
good luck! :)
UPDATE
login.js
define(function(require)
{
var ko = require('knockout');
var auth = require('authentication');
var username = ko.observable();
var password = ko.observable();
return {
username: username,
password: password,
submitForm: function()
{
// Do a login, if success, auth module will take care of it
// and here will take of the error
auth.login(username(), password()).error(function()
{
// notify user about the error (e.g invalid credentials)
});
}
};
});
Authentication.js
define(function(require)
{
var app = require('durandal/app');
return {
init: function()
{
// Initialize authentication...
return system.defer(function(dfd)
{
// Check if user is authenticate or if there has stored token
var isAuthenticate = someOtherFunctiontoCheck();
if (isAuthenticate)
{
dfd.resolve(true); // return promise
}
else
{
// When not authenticate, set root to login page
app.setRoot('views/login');
}
}
},
login: function(username, password)
{
// do authenticate for login credentials (e.g for retrieve auth token)
return $.ajax({
url : 'api/login',
type : 'POST',
data : {
username: username,
password: password
}
}).then(function(token){
// on success, stored token and set root to shell
functionToStoreToken(token);
// Set root to shell
app.setRoot('views/shell');
});
}
};
});

Error calling signout after gapi.auth.authorize

I'm using client side login for google+. The access token expires in 1 hour. Calling gapi.auth.signOut() does not log the user out after the token has expired. I'm trying to re-authorise the user if his token has expired by calling gapi.auth.authorize with client_id, scope and immediate = true parameters. After calling this method, gapi.auth.signOut() doesn't work. I am not able to understand why is it.
Here is the code:
var google = {
signOut: function() {
var token = gapi.auth.getToken();
if(!token) {
var params = {
'client_id': global.clientid,
'session_state': global.sessionState,
'response_type':'token'
}
gapi.auth.checkSessionState(params, function(state){
if(state == true) {
google.doSignOut();
} else {
google.silentAuthorize(function(data){
google.doSignOut();
});
}
});
} else {
google.doSignOut();
}
},
doSignOut: function() {
gapi.auth.signOut();
google.loggedin = false;
},
silentAuthorize: function(callback) {
var params = {};
params.client_id = global.clientid;
params.immediate = true;
params.scope = "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/plus.login"
gapi.auth.authorize(params, callback);
}
}
calling google.signOut works fine while the token hasn't expired. But once the token has expired or I simple call google.silentAuthorize() with a callback, calling gapi.auth.signOut() starts throwing an error:
TypeError: Cannot read property 'clear' of null
Been trying to figure this out for 4 hours now, any help is highly appreciated!
I couldn't find anyway to renew token from front end, so I switched to hybrid method of using google auth. I now revive the session every time it is about to expire within php.

Logout from external login service (Gmail, facebook) using oauth

I have an ASP.NET MVC 4 application that allows users to login with external service like Gmail.
So far, the user is able to login and navigate inside the application. But The problem is in logout. I have a button to logout that request call the controller action LogOff() inside my AccountController. Inside that method, how can I logout if the user is authenticated via oauth?
With a local account, I use:
public ActionResult LogOff()
{
WebSecurity.Logout();
return RedirectToAction("Login", "Account");
}
But with oauth I don't see anything similar...
I think I need to clear some kind of cookie but I don't know how...
Based on this, I implemented the following client-side solution (I'm asking previously if the user want to logout also in the provider):
//get accountType, accessToken, redirectUrl and clientID
var accountType = ...;
var accessToken = ...;
var redirectUrl = ...;
var clientID = ...;
$("#logoutConfirmButton").on('click', function () {
externalLogout();
});
function externalLogout() {
var url, params;
if (accountType== "facebook") {
url = "https://www.facebook.com/logout.php";
params = {
next: redirectUrl,
access_token: encodeURIComponent(accessToken)
};
performCallLogout(url, params, accountType);
} else if (accountType== "google") {
url = "https://www.google.com/accounts/Logout?continue=https://appengine.google.com/_ah/logout";
params = {
next: redirectUrl
}
performCallLogout(url, params, accountType);
} else if (accountType == "microsoft") {
url = "https://login.live.com/oauth20_logout.srf";
params = {
clientId: clientID,
redirectUrl: redirectUrl
}
performCallLogout(url, params, accountType);
}
}
function performCallLogout(url, params, accountType) {
if (accountType == "facebook") {
window.location.href = url + "?next=" + params.next + "&access_token=" + params.access_token;
} else if (accountType == "google") {
window.location.href = url + "?continue=" + params.next;
} else if (accountType == "microsoft") {
window.location.href = url + "?client_id=" + params.clientId + "&redirect_url=" + params.redirectUrl;
}
}
Hope this help someone.
WebSecurity.Logout(); will log out the user even if they authenticated through OAuth.
If you want to be sure the token does not persist after logout you can call
Session.Remove("facebooktoken"); //Facebook example
The information is from this webpage. Some more details worth reading on there too.
Sounds like you want to log the user out of the source authenticating site? Only the authenticating site can delete/modify its cookies.
The solution will be to redirect the user to the logout page for the authenticating site, or use an API script to log the user out (if one exists for that site.) You could use a form with the "target" attribute to open a new window if you don't want the main browser window to redirect.
FaceBook, for example, has an API call:
FB.logout(function(response) {
// user is now logged out
});
The MVC FaceBook client has a method GetLogoutUrl, too, which returns a URL you could use on the server side.