I'm trying to build a single ReactJs form-component (writing in TypeScript targeting ES6) which I will use for all forms in my web-application.
In order to handle errors when a form is posted, I need to check for errors in case there are any.
This is my handleSubmit method:
private handleSubmit = (ev: React.FormEvent<HTMLFormElement>): void => {
var theForm = ev.target as HTMLFormElement;
const formData = new FormData(theForm);
fetch(theForm.action, { method: theForm.method, body: formData })
.then(response => {
if (!response.ok)
throw Error(response.statusText);
return response;
})
.catch(reason => console.log(reason));
}
The action I am currently working on is a simple POST-action (example):
[HttpPost]
[AllowAnonymous]
public async Task<ActionResult> Login(RequestAccountLoginPost request)
{
throw new ApplicationException("The application crashed in a way nobody expected. Sorry about that...");
}
However, when submitting the form, I always end up with the ASP.net standard error-page.
How I can stop this behavior and stay on the page the form is on showing some errors e.g. There was an internal server-error. Write an e-mail to help#me.please which helps the user indentifying that something went wrong?
Later I want to implement some handling in case server-side validation fails (but client-side not) so I can show the user: Look. This value is wrong. Please fix.. Therefore I do need to stay on the same page.
I'm new to using fetch-api. Maybe this is not the best choosen option for this?
You need to prevent the normal submission of the form and handle everything in JS, otherwise your page will post by default the data to your server.
private handleSubmit = (ev: React.FormEvent<HTMLFormElement>): void => {
var theForm = ev.target as HTMLFormElement;
ev.preventDefault()
// ... your code
From MDN: The Event interface's preventDefault() method tells the user
agent that if the event does not get explicitly handled, its default
action should not be taken as it normally would be.
Related
I have a NextJS application that sometimes isn't working as expected.
When I'm with a slow connection and the first load time of the site is bigger than normal, when I try to login into application, the default behavior of HTML form is executed and the credentials that I inserted are shown on the URL, even though I've a event.preventDefault() in the submit function and I'm not using GET.
I've tried to improve the performance of the app and reduce the first time load of the pages, but, if the user can make the loading time slow, it can be exploitable.
I just want to prevent that the credentials be shown on URL.
It can be replaced with any type of another error.
Here is my code:
async function handleLogin(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setIsLoadingLogin(true);
setError('');
const captchaValue = await captchaRef.current?.executeAsync();
if (!captchaValue) {
setError('Erro de captcha. Tente novamente mais tarde.');
return setIsLoadingLogin(false);
}
try {
const { access, refresh } = await loginService({
email,
password,
captcha_value: captchaValue,
});
setCookie(undefined, cookieNames.userAccessToken, access);
setCookie(undefined, cookieNames.userRefreshToken, refresh);
await router.push('/home');
} catch (error: any) {
if (error.response.status === 500) return setError('Erro no servidor.');
if (error.response.data.detail) return setError(error.response.data.detail);
} finally {
setIsLoadingLogin(false);
setPassword('');
captchaRef.current?.reset();
}
}
<form onSubmit={handleLogin}>
...
</form>
Since you say that it's cause of slow connection - means JS is not yet downloaded while you submit your form.
I see few solutions:
Add method="POST" to your form - still submit is executed not via JS, but via HTML form
Initially hide your form or make submit button disabled. Then when JS is loaded enable form
use btoa() and atob() js functions to encrypt and decrypt
👋 I am using Google Identity Services, and facing some problems. Have a look at the function below to loginUser and get the access_token:
const client = (window as any).google.accounts.oauth2.initTokenClient({
client_id: process.env.GOOGLE_CLIENT_ID,
scope: `profile email`,
callback: '' // defined at request time
});
const loginUser = async () => {
const tokenResponse = await new Promise<TokenResponse>((resolve, reject) => {
try {
// Settle this promise in the response callback for requestAccessToken()
client.callback = (resp) => {
if (resp.error !== undefined) {
reject(resp);
}
resolve(resp);
};
// requesting access token
client.requestAccessToken({ prompt: 'consent' });
} catch (err) {
console.log(err)
}
});
return tokenResponse;
}
Invoking loginUser() causes a new pop-up.
If the user selects an account, I get the tokenResponse (which contains access_token). Works great. 🚀
But if the user closes the pop-up, the Promise never resolves, since we are waiting for the callback to fire, which never happens. 😥
Is there a way we could detect if the user has closed the pop-up?
I think you can do something in the "error_callback". You can find details at: Handle Errors
const client = google.accounts.oauth2.initCodeClient({
client_id: 'YOUR_GOOGLE_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
ux_mode: 'popup',
callback: myCallback,
error_callback: myErrorCallback // You can do something when popup window closed
});
(Update) Prospective Solution
It looks like the google developers have added the error handlers now into the new Google Identity Services. :)
Checkout the documentation at https://developers.google.com/identity/oauth2/web/guides/error.
(I still haven't tested it. Hence putting it as a prospective solution). Happy coding!
Original Answer
Here are the two solutions which you can consider if you're facing this issue.
Solution 1
Go back to the old gapi based login. (Not recommended, as it will be deprecated soon). For more details, on deprecation, refer to this blog by Google.
Solution 2
We add a javascript focus event listener just after opening the popup. So, whenever the user closes the popup and returns to the parent window, we shall consider it as client_focused_back_to_window / pop_up_closed event.
The only edge case is when the user doesn't close the popup and directly returns to the window; the focus event listener will be fired. But I think that's okay because if the user again clicks on Sign In with Google button again, the same pop-up window gets reused (thanks to _blank parameter used by Google Identity services while creating the popUp window).
const client = (window as any).google.accounts.oauth2.initTokenClient({
client_id: process.env.GOOGLE_CLIENT_ID,
scope: `profile email`,
callback: '' // defined at request time
});
/**
* Function to login the user and return the tokenResponse
*
* It throws error if the login fails or the user cancels the login process
*/
const loginUser = async () => {
const tokenResponse = await new Promise<google.accounts.oauth2.TokenResponse>(
(resolve, reject) => {
const focusEventHandler = () => {
reject({
error: 'client_focused_back_to_window',
});
window.removeEventListener('focus', focusEventHandler); // removing the event listener to avoid memory leaks
};
// adding an event listener to detect if user is back to the webpage
// if the user "focus" back to window then we shall close the current auth session
window.addEventListener('focus', focusEventHandler);
// Settle this promise in the response callback for requestAccessToken()
client.callback = (resp) => {
if (resp.error) {
reject(resp);
}
resolve(resp);
};
// requesting access token
client.requestAccessToken({ prompt: 'consent' });
},
);
return tokenResponse;
}
PS: We've been using this solution in production, and so far, thousands, if not millions, of users have tried to log in via Google. Everything is working fine so far. 🙂
It appears that this is not working for the current version of GSI.
It did work for the old gapi version and if the popup were to be closed you would get a response with the error: {error: "popup_closed_by_user"}. As referenced in this answer: Google SSO login error: "popup_closed_by_user"
Hopefully adding the #google-oauth tag will allow someone at Google to see this and hopefully update this script.
Please see other referenced question: Google Oauth popup cancellation callback
This is referring to the documentation on https://developers.google.com/identity/oauth2/web/guides/use-code-model#trigger_oauth_20_code_flow and https://developers.google.com/identity/oauth2/web/guides/use-token-model#initialize_a_token_client
In fact the documentation states: Users may close the account chooser or sign-in windows, in which case your callback function will not be invoked..
Question for Google - how can we detect this?!
I'm attempting to use Stripe as a payment method, on an ASP.Net MVC project
We've gone the route of using stripe elements to define our own look and feel for the card details.
I've followed the tutorials, and have managed to get the card widgit displaying nicely.
Where it fails is on the submission.
I've hooked up the following JS to the button (taken straight from the guides)
var form = document.getElementById('payment-form');
var clientSecret = $("#authcode").val();
form.addEventListener('submit', function(ev) {
ev.preventDefault();
stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: card,
billing_details: {
name: 'Jenny Rosen'
}
}
}).then(function(result) {
if (result.error) {
// Show error to your customer (e.g., insufficient funds)
console.log(result.error.message);
} else {
// The payment has been processed!
if (result.paymentIntent.status === 'succeeded') {
// Show a success message to your customer
// There's a risk of the customer closing the window before callback
// execution. Set up a webhook or plugin to listen for the
// payment_intent.succeeded event that handles any business critical
// post-payment actions.
}
}
});
});
Now when I put a breakpoint on it does enter the stripe.confirmCardPayment function
However I never get a response. I've put a break point on the line if (result.error) { but it never fires. I've sat and left it for over two minutes.
No errors in the chrome console, and also no network traffic.
I am happy that the JS is all loaded correctly because if I hit submit again the Stripe JS lib correctly tells me I have an in-flight confirmCardPayment
Any suggestions are most appreciated.
Looks you are using jQuery at:
var clientSecret = $("#authcode").val();
You should wrap js with
$(document).ready(function(){
....
}):
Right, I've finally got it working.
Yes I believe with the answers given by Nolan / Raphael that it was indeed a caching issue!
However. Deleting the cache with CTRL+SHIFT+DEL had no effect.
Only way I could get the cache to clear was to physically alter the JS.
Thanks again to Nolan / Raphael for their suggestions :)
I need to make a page with a button that fills certain text area with data from database. However I need it to also require administrator privileges.
Is it possible to have an API method that doesn't require credientals, but an Identity session instead? Ideally something that gets a HttpContext.User?
I don't know anything about JavaScript, I managed to put together this thing:
const url = '/api/clients/';
function getItems() {
fetch(url + document.getElementById('input').value)
.then(response => response.json())
.then(data => _displayItems(data));
}
function _displayItems(arrayOfResults) {
const textbox = document.getElementById('textbox');
textbox.value = "";
arrayOfResults.forEach(json => {
textbox.value += json.deviceIdentifier + "\n";
});
}
I have an API endpoint at http://localhost/api/clients/{string} that does a database query and it works as expected. But I can not allow just about anybody to access that data and I have trouble figuring out how to make user:
not provide his credentials
be able to use it with minimal effort (ie. just click the button and get results)
be unable to use this endpoint while not currently logged in on the website
Normally I just use this line to get the user that tries to access the controller:
var currentUser = await _userManager.FindByNameAsync(_userManager.GetUserName(HttpContext.User));
and go from there. But when my JS script is accessing the API it doesn't provide a way to validate using HttpContext, from what I'm seeing.
I have tried the following typical approaches but couldn't able to do a redirect from an asynchronous ASP.NET method:
Response.Redirect("~/Login.aspx");
HttpContext.Current.Response.Redirect("~/Login.aspx");
I have also tried Server.Transfer but couldn't get success due to the unavailability of the page controls inside a reference method(delegate).
I have already tried a static property which I filled in delegate response and continuously checking it on client side using ASP.NET SignalR to perform a redirect but as it a static property, it redirects all the user to the login page which I don't want to do it.
private void Response_Recieved(Message objMessage)
{
try
{
if (objMessage.OperationType == Operation.Data)
{
NotificationMessage objNotifications = new DataProcess().Deserialize_Messages(objMessage);
_jsonData = JsonConvert.SerializeObject(objNotifications);
}
else if (objMessage.OperationType == Operation.ServerAbnormalDisconnect)
{
// I want to redirect a user to login page whenever server disconnects
HttpContext.Current.Response.Redirect("~/Login.aspx");
//Response.Redirect("~/Login.aspx");
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
What would be the alternative or best approach to perform a redirect from a delegate function when there is no control available also without using any static property?
You can't issue a HTTP redirect call from an asynchronous operation in ASP.Net but there are viable alternatives. I will make my answer generic to hopefully help other readers but as a SignalR user you need to look at number 3.
Let's examine 3 scenarios:
An async operation is commenced from within a normal HTTP request using HostingEnvironment.QueueBackgroundWorkItem (.NET 4.5.2 onwards).The resource requested is (where applicable) processed/rendered, and returned. The request is completed. There is no longer any request to redirect. In this scenario, you could perhaps store a value in the Application cache with an expiry time to redirect the user on the next request.
Your clients are connected by way of web socket and your server side implementation uses Microsoft.WebSockets.dll. The connection to the web server is upgraded to a full-duplex socket connection; it is not the url for the page but a comms connection so there is nothing to redirect.Instead, you send a command over the connection informing the client side code that a redirect is needed and you perform the redirect in JavaScript. In the WebSocketHandler, with this example sending a string command:
Send("LOGOFF");
and in the JavaScript ws.onmessage handler, identify the message as "LOGOFF" and change the window.location.href to the target page:
ws.onmessage = function (message) {
switch (message.data) {
case "LOGOFF":
location.href = "Login.aspx";
}
};
The above example is simplified. I have a site which does this and actually send a class (JSON serialised) with a command type and optional payload.
SignalR has the same issues as #2 and I would propose a similar solution. I've not worked with SignalR yet but according to a comment on this answer you would send the command like so:
GlobalHost.ConnectionManager.GetHubContext<Chat>().Clients.Client(connectionId)‌​.addMessage("LOGOFF");
Look out for the LOGOFF message in your SignalR client-side message handler and set the window location href to your login page.
The Response.Redirect method uses a ThreadAbortException to stop the execution of the current request.
As you are catching that exception and just absorbing it, the request handling will just go on as usual and ignore the redirect that you tried to do.
You can use a variable to flag your desire to do the redirect, and then perform it outside the try...catch:
private void Response_Recieved(Message objMessage) {
bool doRedirect = false;
try {
if (objMessage.OperationType == Operation.Message_Response) {
NotificationMessage objNotifications = new DataProcess().Deserialize_Messages(objMessage);
_jsonData = JsonConvert.SerializeObject(objNotifications);
} else if (objMessageBo.OperationType == Operation.ServerAbnormalDisconnect) {
// I want to redirect a user to login page whenever server disconnects
doRedirect = true;
}
} catch (Exception ex) {
Logger.WriteException(ex);
}
if (doRedirect) {
HttpContext.Current.Response.Redirect("~/Login.aspx");
}
}
Here is what I had done years back. I am posting it here to help others who are asking me.
Well it is clearly can't be done from an asynchronous ASP.NET method(delegate).
So to achieve the desire functionality I passed a value to the client side from the method using normal broadcasting in SignalR.
And on client side, I am validating and performing actions accordingly. I have changed the URL using simple JavaScript. I am putting a simplest code to understand the core concept of redirection from ASP.NET using SignalR.
Code Behind
[HubMethodName("getSessionState")]
public string GetSessionState(string status) {
return Clients.Caller.UpdatedState(status);
}
private void Response_Recieved(Message objMessage)
{
try
{
if (objMessage.OperationType == Operation.Data)
{
NotificationMessage objNotifications = new DataProcess().Deserialize_Messages(objMessage);
_jsonData = JsonConvert.SerializeObject(objNotifications);
SendNotifications(_jsonData);
}
else if (objMessage.OperationType == Operation.ServerAbnormalDisconnect)
{
GetSessionState("false"); //<--- Disconnecting session by passing false for sessionstate
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
var notifications = $.connection.notificationsHub;
notifications.client.updatedState = function (status) {
if (status === "false") {
window.alert("Server is disconnected. Forced logout!");
window.location.href = "/logout.aspx"
}
else {
// Doing my stuff here...
}
};