javascript "callbacks" across redirecting / after reloading - javascript

I've got a site (asp.net mvc razor) on wich some functionalities require authorization / login.
These functionalities can be started by clicking on a button for example.
By clicking on such a button, the system checks whether the user is logged in or not.
If not, the user is redirected to the login page where he can sign in.
After that he will be redirected to the initial page again without initiating the users action.
So heres the workflow:
->Page x -> button y -> click -> redirect to login -> login -> redirect to x.
The redirects are simple Url.Action() statements.
What I want to do is to dynamically redirect to the page the click came from and ideally jump to the senders selector in order to simplify things for users.
What possibilities do I have to achieve this?
Only things coming to my mind are quite ugly stuff using ViewBag and strings
Update:
Info: As storing session variables causes problemes concerning concurrent requests this feature is disabled solution wide so I cannot use session variables.
Besides: One of the main problems is, that I cannot sign in without making an ajax call or sending a form. And by sending a form or making an ajax call I loose the information about the original initiator of the action and the parameters.
I solved this by adding by adding this to all such actions in their controllers:
[HttpPost]
public ActionResult ActionA(Guid articleId, Guid selectedTrainerId)
{
//if user is not authenticated then provide the possibility to do so
if (!Request.IsAuthenticated)
{
var localPath = this.ControllerContext.RequestContext.HttpContext.Request.Url?.LocalPath;
var parameter = this.ControllerContext.RequestContext.HttpContext.Request.Params["offeringRateId"];
var returnUrl = localPath + "?articleId=" + parameter;
return PartialView("LoginForOfferingPreview", new LoginForOfferingPreviewViewModel
{
RequestUrl = returnUrl,
//this will be used in the view the request was initiated by in order to repeat the intial action (after login has been successfull)
Requester = OfferingPreviewRequester.CourseTrialAdd,
//this will be used in the view to initiate the request again
RequestParameters = new List<dynamic> { new { articleId = articleId },new { selectedTrainerId = selectedTrainerId }}
});
}
//actual action
SendBasketEvent(new CourseAddMessage
{
BasketId = BasketId,
OfferingRateId = articleId,
SelectedTrainerId = selectedTrainerId,
SelectedTime = selectedTime,
Participants = selectedParticipants,
CurrentDateTime = SlDateTime.CurrentDateTimeUtc(SlConst.DefaultTimeZoneTzdb),
ConnectionId = connectionId
}, connectionId);
return Json(JsonResponseFactory.SuccessResponse());
}
the hereby returned view for login contains following js code that is called if the login has been succesfull:
function onLoginFormSubmit(data) {
//serialize form containing username+pw
var datastring = $("#loginForm").serialize();
$.ajax({
type: "POST",
url: '#Url.Action("Login_Ajax","Account",new {area=""})',
data: datastring,
success: function (data) {
debugger;
// display model errors if sign in failed
if (!!!data.Success) {
$(".buttons-wrap").append('<span id="loginFormError" style="color:red;"></span>');
$("#loginFormError").append(data.ErrorMessage);
}
//call method of initiating view that will decide what to dow now
if (data.Success) {
var parametersObjectAsString = JSON.parse('#Html.Raw(JsonConvert.SerializeObject(Model.RequestParameters))');
window.onLoginForOfferingPreviewSuccess('#Model.RequestUrl', parametersObjectAsString, '#((int)Model.Requester)');;
}
},
error: function () {
}
});
}
this works fine as long sigining does not fail due to wrong username or pw.
If that happens, the view shows the errors but by now signing in again somethign really strange happens:
At first it seems to work exaclty like signing in successfully by the first time but then the ajax calls in window function onLoginForOfferingPreviewSuccess will always reach the error block without beeing able to tell you why.
Fiddler reveals weird http resonse codes like 227,556 or something
Thx

Related

Unity and Google OAuth2 Authorization Code Flow

I am trying to build a Unity application to be deployed with WebGL. I am trying to incorporate Google Sign-In into the application, and so far, this was what I've managed to make work in the Unity WebGL build in Chrome:
User presses on the "Login with Google" button on Unity application, in Tab A.
User is directed to Google Sign In page on another Tab B.
User signs in with Google account, and is redirected to my redirect_uri, which is simply https://localhost, with the auth code parameter.
My question is, is it possible for me to do the following, possible with .jslib files:
Instead of going to redirect_uri on Tab B, instead go back to Tab A without reloading, passing along the auth code.
Building on the line above, have javascript handlers, that:
When auth code is received, initiate request to exchange auth code for the id_token as instructed here.
When id_token is received, call a C# Script function to do further actions with the id_token.
Alternatively, I can set redirect_uri to be an endpoint on my backend server, and perform the auth token -> id_token flow using the Google client SDKs. However, for this approach, I would like to know if i am able to
After the auth token -> id_token flow is completed on the backend server, close the current window, Tab B, and go back to Tab A.
After we’re back on Tab A, redirect Unity to a specific scene (not the login scene anymore, but a home page that users are directed to after they are authenticated).
Would very much appreciate any help i can get :')
EDIT: For better clarity, what I want to achieve is something that FacebookSDK for Unity has done in their FB.LogInWithReadPermissions(). The whole auth code -> access_token flow is seamless, and i get redirected back to the Unity application in Tab A at the end with the access_token.
I managed to find a Javascript solution to achieve my first method. The differences are that because
My application will never be in production
Consistency with my Facebook OAuth implementation,
I used the implicit flow instead of the authorization code flow, despite it being not the recommended way due to security concerns. However, I think you can easily use the authorization code flow, retrieving the authorization code and passing it on to your backend to exchange for an id token. (as far as I know, you cannot use Javascript/XHR requests to do this exchange)
So, the flow is that from my C# script, I call a Javascript function from a .jslib file. Basically, the function detects when the OAuth window has redirected back to my redirect_uri, then gets the access_token parameter from the redirected URI, and calls a C# Script function. From there, you should be able to do whatever you need to do (change scene, send to your backend, etc.). Note that there is a try/catch because there will be errors if you attempt to get information from the Google Sign In pages.
The file is as follows:
mergeInto(LibraryManager.library, {
OpenOAuthInExternalTab: function (url, callback) {
var urlString = Pointer_stringify(url);
var callbackString = Pointer_stringify(callback);
var child = window.open(urlString, "_blank");
var interval = setInterval(function() {
try {
// When redirected back to redirect_uri
if (child.location.hostname === location.hostname) {
clearInterval(interval) // Stop Interval
// // Auth Code Flow -- Not used due to relative complexity
// const urlParams = new URLSearchParams(child.location.search);
// const authCode = urlParams.get('code');
// console.log("Auth Code: " + authCode.toString());
// console.log("Callback: " + callbackString);
// window.unityInstance.SendMessage('Auth', callbackString, authCode);
// Implicit Flow
var fragmentString = child.location.hash.substr(1);
var fragment = {};
var fragmentItemStrings = fragmentString.split('&');
for (var i in fragmentItemStrings) {
var fragmentItem = fragmentItemStrings[i].split('=');
if (fragmentItem.length !== 2) {
continue;
}
fragment[fragmentItem[0]] = fragmentItem[1];
}
var accessToken = fragment['access_token'] || '';
console.log("access_token: " + accessToken);
child.close();
// Invoke callback function
window.unityInstance.SendMessage('Auth', callbackString, accessToken);l
}
}
catch(e) {
// Child window in another domain
console.log("Still logging in ...");
}
}, 50);
}
});
Then, in my C# script, I call this function using the following:
public class GoogleHelper : MonoBehaviour
{
[DllImport("__Internal")]
private static extern void OpenOAuthInExternalTab(string url, string callbackFunctionName);
// ...
public void Login(string callbackFunctionName) {
var redirectUri = "https://localhost";
var url = "https://accounts.google.com/o/oauth2/v2/auth"
+ $"?client_id={clientId}"
+ "&response_type=token"
+ "&scope=openid%20email%20profile"
+ $"&redirect_uri={redirectUri}";
OpenOAuthInExternalTab(url, callbackFunctionName);
}
// ...
}
Of course, this is super hacky, and I'm not very familiar with Javascript and so don't really know the implication of the code above, but it works for my use case.

Integrate Salesforce registration page with VanillaJS oidc-client-js, getting the error - No matching state found in storage

Integrate Salesforce registration page with VanillaJS, getting the error - No matching state found in storage
We are redirecting the user to Salesforce registration page when Create Account button is created.
Once the User registers in Salesforce, the user is redirected to our site but we are getting this error. ('No matching state found in storage').
We tried the below solution but still getting the same error.
As I stated in my answer, the oidc client maintains state information
in the local storage so that it can verify that it got the response
back from the intended server. You can mimic this by generating a
secure random string and saving it in localStorage. Do this before
sending a request to your auth server to register a new user.
Reference- Integrate third party login in from my registration page with IdentityServer4 and Angular 6 - 'No matching state found in storage'
Is there a function related to creating registration? How to fix this issue?
Thanks.
Appreciate your help.
After spending days on this issue. Finally found the workaround as registration is not a feature of OIDC.
To overcome this issue, need to follow the Sign In process same as for Sign Up process, created the startSignupMainWindow method same as startSigninMainWindow and passing the signUpFlag:true as shown below in code.
/* This function is written to mimic the oidc library sign in process flow */
function startSignupMainWindow() {
var someState = {
message: window.location.href,
signUpFlag: true
};
mgr.signinRedirect({
state: someState,
useReplaceToNavigate: true
}).then(function() {
log("signinRedirect done");
}).catch(function(err) {
log(err);
});
}
Reading the signUpFlag:true in UserManager.js and swapping the Salesforce Sign In page Url with Sign Up page url and calling the Register function in Code.
UserManager.js(oidc - client - dev - js / src / UserManager.js)
//UserManager Customised Code :
return this.createSigninRequest(args).then(signinRequest => {
Log.debug("UserManager._signinStart: got signin request");
navigatorParams.url = signinRequest.url;
navigatorParams.id = signinRequest.state.id;
if (signinRequest.state._data.signUpFlag) {
register(signinRequest.state._id, signinRequest.state._code_challenge);
} else {
return handle.navigate(navigatorParams);
}
})
The below code is Register function written in code.
/* This function is written to send the code_challenge to salesforce server so that
salesforce server holds the code challenge and used to verify the further requests(token-request)
against the code_challenge it received initially.*/
//Customised register function written outside the library (Inside our App):
function register(_id, code_challenge) {
var date = new Date();
var baseUrl = "SALESFORCE_URL/login/SelfRegister?expid=id";
var expId = "id";
var userPage = encodeURIComponent(window.location.href);
var appDetails = "response_type=code&" +
"client_id=CLIENT_ID" +
"client_secret=CLIENT_SECRET&" +
"redirect_uri=CALLBACK_URL&" +
"state=" + _id + "&code_challenge=" + code_challenge + "&code_challenge_method=S256&response_mode=query";
var encodedapp = encodeURIComponent(appDetails);
var startUrl = "/services/oauth2/authorize/expid?" + encodedapp;
var signUpUrl = baseUrl + "&startURL=" + startUrl;
window.open(signUpUrl, "_self");
};

Microsoft authorization with new tab

I need to implement login functionality with Microsoft via a new tab.
Currently our login process works with Microsoft in a way which is not really suited for our needs. The user clicks a login button, gets redirected to the Microsoft sign in page, enters his information and gets redirected back to our login page. This all happens on the same tab. In the code bellow is an example of this:
authenticateOffice(finallyCallback?: () => void): void {
finallyCallback = finallyCallback || (() => {
});
let currentUrl = location.href;
this.redirectLocationUrl = currentUrl.substr(0, currentUrl.indexOf('#'));
let url = "https://login.microsoftonline.com/".concat(this.office365Domain, "/oauth2/v2.0/authorize?client_id=", this.clientId,
"&response_type=id_token&redirect_uri=", this.redirectLocationUrl, "&response_mode=fragment&scope=openid profile&state=12345&nonce=678910");
window.location.href = url;
}
Pretty straightforward and nothing too complex.
What we need to do in layman's terms is:
1. Open a new tab when clicking the login button
2. User enters his information on the Microsoft login page
3. When authorized, tab is automatically closed
4. Our login page then receives the "id_token" from Microsoft from oauth2 and with this I do the rest of my login logic
else if (!StringHelper.isNullOrEmpty(window.location.hash) && window.location.hash.startsWith('#id_token')) {
let response = window.location.hash;
this._office365Service.getOfficeId(response)
.subscribe((result: string) => {
let key = result;
if (key) {
this._loginService.processOfficeIdResult(key);
} else {
abp.message.warn(this._localizationService.localize('Office365User', this.localizationSourceName));
}
});
}
My question is this:
How can we regain or rather take control of this "new" tab and do something with it?
How can we know when authorization is complete?If it fails or is successful?
In my previous research all i could come by was the callback approach but how can you send a function to a new tab which knows when to close the tab?And how can you receive authorization information when the user is successfully authenticated?
var win = window.open("window.html");
And then what? You write a function which calls itself every second and checks for input?
(just throwing ideas here)

jQuery post within post done function

I'm trying to implement and figure out something that I've never done before. I have a sense how to do it but I'm not exactly sure how to do so...
So I have a button on my website where users perform authorization of my application. When user goes to xx website and authorizes my application I would like to perform a jQuery post to this xx website every 3 seconds checking whether the user has performed authorization of my app to use it.
If the response from the website is success i'd like to make the button enabled and clickable to proceed to final step to insert some record into the DB.
So the current code looks like this:
$(document).on("click", "#btnClick", function (e) {
$(this).prop('disabled', true);
e.preventDefault();
$.post("/GetMyData/GenerateSession")
.done(function (sessionID) {
window.open("https://example.com/Authorize?=" + data, "_blank");
$('#btnClick').hide();
$('#btnAuthorize').show();
$("#step2").removeClass("disabled").addClass("selected");
});
});
So as you can see when SessionID is generated in my aciton, I simply open up a new tab in my browser and take him to the corresponding website.
Now what I'd like to do in final step is remove this code where user himself has to click the button to authorize the application like this:
$(document).on("click", "#btnAuthorize", function () {
$.post('/GetMyData/StoreToken')
.done(function (data) {
if (data.result == "Success") {
window.location.href = '/Success';
}
});
});
So without having the user to perform this final step. I'd like to make my application do this for user and perform a post every 3 seconds once he #btnClick even has been triggered..
How could I do this with jQuery?
P.S. I'd like to do this because I've noticed that some people simply don't authorize the application for usage but still press this button ,and then errors occur, which is why I'd like to disable usre clicking this button unless he indeed authorized the app.
Something like this should do it.
var r = window.setInterval(function(){
$.post('/GetMyData/StoreToken')
.done(function (data) {
if (data.result == "Success") {
window.location.href = '/Success';
}
});
},3000);
If you want to perform some function every X time you have to use setTimeout/setInterval functions of javascript (https://www.w3schools.com/jsref/met_win_settimeout.asp)

Calling the app config method inside ajax response - AngularJS

I am developing an app using angularjs and this is my first hands on using angular. Although, I have started understanding it and have developed some part of the app but I am stuck at one particular point.
I am trying to implement login functionality, so as the page loads, I am authenticating user and redirecting him to login page. On successful login, I am storing some values of user in one of the config provider.
Now I am using an API which has their own method of authentication and they have expose the ajax method which I can use to authenticate a user.
I have provided a snippet below. What I am primarily doing is using the external API, authenticating the user and once authenticated, I am getting roles associated to that user using another ajax method of the API, called "GetUserDetails".
And inside the response of the "GetUserDetails", I am injecting a provider and setting some values, so I can use this across my app.
The problem here is the app.config method is never called/executded. I mean the ajax request is returning response, and the alert is displayed on my page, but app.config is never executed.
But the same app.config if I call inside the done() of GetUser method, the app.config gets executed and stores values in my provider. But I want the GetuserDetails values also to be stored before I do anything in my app as I want to execute certain functionality based on user.
Below is my function in main.js file
function(angular,angularRoute,app,routes,configService){
var $html = angular.element(document.getElementsByTagName('html')[0]);
angular.element().ready(function() {
$.c.authentication.getUser()
.done(function(response){
if(response.userName!="anonymous"){
$.c.ajax({
method: "GetUserDetails",
parameters: {
User: response.user
}
})
.done(function(res) {
alert("I have reached the destination").
app.config(['configServiceProvider', function(configServiceProvider){
configServiceProvider.setLoginStatus(true);
configServiceProvider.setUserName(response.userName);
configServiceProvider.setUserObject(response);
configServiceProvider.setUserRoleDetails(res);
}]);
})
.fail(function(res) {
alert("Error while getting user roles ."+res);
});
angular.resumeBootstrap([app['name']]);
}
else
{
app.config(['configServiceProvider', function(configServiceProvider){
configServiceProvider.setLoginStatus(false);
configServiceProvider.setUserName(response.userName);
}]);
//Show Login Screen
var url = window.location.href.split("#")[0];
window.location.href = url + "#/Login";
angular.resumeBootstrap([app['name']]);
}
})
.fail(function(response){
$rootScope.isLoggedIn=false;
});
});
Here is my configServiceProvider
define(['../app'],function(app){
return app.provider('configService', function(){
var options={};
this.setLoginStatus = function(status){
//$rootScope.isLoggedIn = status;
options.isLoggedIn=status;
};
this.setPreLoginInfo=function(info){
options.preLoginInfo=info;
};
this.setUserName=function(name){
options.username=name;
}
this.setUserObject = function(userObject) {
options.userObject = userObject;
}
this.setUserRoleDetails = function(userRoleDetails) {
options.userRoleDetails = userRoleDetails;
}
this.$get=[function(){
if(!options){
}
return options;
}];
});
})
Can anyone please explain me what's going wrong here or what I am missing ?
Also, is there any alternative to achieve the same functionality ?
No luck in figuring out why the above scenario was not working. Since I had already spent lot of time behind this, I have found a workaround to achieve the same with the use of services.

Categories