I'm very confused about the Recaptcha V3 implementation and it is not clear to me if actually need to implement it on my website or if initializing Appcheck with my Recaptcha V3 credentials is enough: Appcheck does successfully initialize and I have enforced firestore and the cloud storage to use it.
I don't want bots to create infinite accounts on my website and raise like crazy my costs so I looked into implementing Recaptcha on forms: the developer documentation is a joke (https://developers.google.com/recaptcha/docs/v3) as it is not explained how to verify the token which is returned
I saw an old article from 2017 telling you to use Cloud Functions (which may take up to 10-12 seconds to fire up in case of cold-start) but this sounds really far-fetched and 5 years later I hope we have a better solution: https://firebase.googleblog.com/2017/08/guard-your-web-content-from-abuse-with.html
Am I overthinking this? Would Appcheck protect my app from people abusing my contact form and sign up section? If this is not enough, how can I implement Recaptcha V3 with React and Firebase?
I am using Next JS and so far my code looks something like this (where I replaced my publishable key "mySyteKey"):
import Script from "next/script";
export default function TestRecaptcha() {
const handleSubmit = (e) => {
e.preventDefault();
grecaptcha.ready(function () {
grecaptcha.execute('mySiteKey', {action: 'submit'}).then(function (token) {
// How the hell do I verify this token!?
console.log(token)
}), error =>{
console.log(error.message)
}
});
}
return (
<div>
<Script src="https://www.google.com/recaptcha/api.js?render=mySiteKey"
strategy="beforeInteractive"/>
<form onSubmit={(e) => handleSubmit(e)}>
<button
type="submit">
Submit
</button>
</form>
</div>
)
}
Related
As the title suggests, how do you create a password protected React website? Nothing fancy beyond that. Everyone would have access to the same password, no need to do anything super secure, just simply to access the content on the React app.
This is new to me, so I'm not sure the proper steps to take. This is just to protect the content in on the frontend, and it doesn't need to be super secure, mostly to make sure we weed out any truly unauthorized access while our event is going on.
You could add the password-checking logic to your login component. Here are some ideas
Opt 1: use a library, see react-app-protect
Note: this currently only works with React 17 or lower
Opt 2: using input + DOM element search
import React, { useState } from "react";
const Login = () => {
const [isVerified, setIsVerified] = useState(false);
const checkPw = () => {
// gets the current input value
const answer = document.getElementById("password").value;
if (answer === "yourSecretPassword") {
setIsVerified(true);
} else {
alert("Sorry, that's not it");
}
};
return (
<>
{isVerified ? <YourApp />
:
(
<form onSubmit={checkPw}>
<input id="password" name="password" />
<button>open sesame</button>
</form>
)
}
</>
;
^ This is pretty janky, but it sounds like you're not worried about best-practices.
Context & Reproducible Scenario
I'm using the combination of these libraries and tools:
NextJS 12+ (based on React 18+)
MSAL-Browser 2.25+ and MSAL-React 1.6+ (Microsoft's libs for OpenID login against Azure B2C)
I'm using the Auth Code + PKCE redirect flow so this is the flow for users:
They land on /, the home page
They click a /me router link
They go to Azure B2C to log in because said page has this logic:
<MsalAuthenticationTemplate
interactionType={InteractionType.Redirect}
authenticationRequest={loginRequest}>
where loginRequest.state is set to router.asPath (the "intended" page: /me)
Note that the page is also wrapped in a <NoSsr> component based off Stack Overflow.
User logs in on Azure B2C, gets redirected back to my app at / (the root)
⛔ Problem: the user now briefly sees the / (home) page
After a very brief moment, the user gets sent to /me where they are signed in
The MSAL docs don't seem to have much on the state property from OIDC or this redirect behavior, and I can't find much about this in the MSAL sample for NextJS either.
In short: the issue
How do I make sure MSAL-React in my NextJS application send users to the "intended" page immediately on startup, without briefly showing the root page where the Identity Server redirects to?
Relevant extra information
Here's my custom _app.js component, which seems relevant because it is a component that triggers handleRedirectPromise which causes the redirect to intended page:
export default function MyApp({ Component, pageProps }) {
return (
<MsalProvider instance={msalInstance}>
<PageHeader></PageHeader>
<Component {...pageProps} />
</MsalProvider>
);
}
PS. To help folks searching online find this question: the behavior is triggered by navigateToLoginRequestUrl: true (is the default) in the configuration. Setting it to false plainly disables sending the user to the intended page at all.
Attempted solutions with middleware
I figured based on how APP_INITIALIZERs work in Angular, to use middleware like this at some point:
// From another file:
// export const msalInstance = new PublicClientApplication(msalConfig);
export async function middleware(_request) {
const targetUrlAfterLoginRedirect = await msalInstance.handleRedirectPromise()
.then((result) => {
if (!!result && !!result.state) {
return result.state;
}
return null;
});
console.log('Found intended target before login flow: ', targetUrlAfterLoginRedirect);
// TODO: Send user to the intended page with router.
}
However, this logs on the server's console:
Found intended target before login flow: null
So it seems middleware is too early for msal-react to cope with? Shame, because middleware would've been perfect, to allow as much SSR for target pages as possible.
It's not an option to change the redirect URL on B2C's side, because I'll be constantly adding new routes to my app that need this behavior.
Note that I also tried to use middleware to just sniff out the state myself, but since the middleware runs on Node it won't have access to the hash fragment.
Animated GIF showing the flashing home page
Here's an animated gif that shows the /home page is briefly (200ms or so) shown before /me is properly opened. Warning, gif is a wee bit flashy so in a spoiler tag:
Attempted solution with custom NavigationClient
I've tried adding a custom NavigationClient to more closely mimic the nextjs sample from Microsoft's repository, like this:
import { NavigationClient } from "#azure/msal-browser";
// See: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-react/docs/performance.md#how-to-configure-azuremsal-react-to-use-your-routers-navigate-function-for-client-side-navigation
export class CustomNavigationClient extends NavigationClient {
constructor(router) {
super();
this.router = router;
}
async navigateInternal(url, options) {
console.log('👍 Navigating Internal to', url);
const relativePath = url.replace(window.location.origin, "");
if (options.noHistory) {
this.router.replace(relativePath);
} else {
this.router.push(relativePath);
}
return false;
}
}
This did not solve the issue. The console.log is there allowing me to confirm this code is not run on the server, as the Node logs don't show it.
Attempted solution: go through MSAL's SSR docs
Another thing I've tried is going through the documentation claiming #azure/msal-react supports Server Side Rendering (SSR) but those docs nor the linked samples demonstrate how to solve my issue.
Attempted solution in _app.tsx
Another workaround I considered was to sniff out the hash fragment client side when the user returns to my app (and make sure the intended page is also in that state). I can successfully send the OpenID state to B2C like this...
const extendedAuthenticationRequest = {
...authenticationRequest,
state: `~path~${asPath}~path~`,
};
...and see it returned in the Network tab of the dev tools.
However, when I try to extract it in my _app.tsx still doesn't work. I tried this code from another Stack Overflow answer to get the .hash:
const [isMounted, setMounted] = useState(false);
useEffect(() => {
if (isMounted) {
console.log('====> saw the following hash', window.location.hash);
const matches = /~path~(.+)~path~/.exec(window.location.hash);
if (matches && matches.length > 0 && matches[1]) {
const targetUrlAfterOpenIdRedirect = decodeURIComponent(matches[1]);
console.log("Routing to", targetUrlAfterOpenIdRedirect);
router.replace(targetUrlAfterOpenIdRedirect);
}
} else {
setMounted(true);
}
}, [isMounted]);
if (!isMounted) return null;
// else: render <MsalProvider> and the intended page component
This does find the intended page from the state and executes routing, but still flashes the /home page before going to the intended page.
Footnote: related GitHub issue
Submitted an issue at MSAL's GitHub repository too.
I'm using:
firebase: ^8.6.8
next: ^11.0.1
react: ^17.0.2
When trying to sign in with Google on Safari, I get errors related to a Content Blocker with both signInWithPopUp and signInWithRedirect:
Content blocker prevented frame displaying https://my-site.com/page
from loading a resource from https://apis.google.com/js/api.js?onload=__iframefcb318667
Does anybody know how to handle this properly?
Thank you!
Okay, here is the solution I used:
Instead of using signInWithRedirect or signInWithPopup that seem to cause problem on Apple devices using Safari, I directly use [https://apis.google.com/js/platform.js] and then signInWithCredential using the googleUser.
I also used the package react-google-signin [https://www.npmjs.com/package/react-google-login] to simplify my code.
Step by step:
Enable Google Sign-in method on Firebase Console for Authentication
On the Credentials page of Google Cloud Platform [https://console.developers.google.com/apis/credentials], choose your project and under OAuth 2.0 Client IDs get the Client Id.
Save the Client ID as an environment variable NEXT_PUBLIC_GOOGLE_CLIENT_ID in your Project Settings on Vercel.
In your _document.tsx add the https://apis.google.com/js/platform.js script and needed meta data:
<meta name="google-signin-client_id" content={process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}></meta>
<meta name="google-signin-cookiepolicy" content="single_host_origin"></meta>
<meta name="google-signin-scope" content="profile email"></meta>
<script src="https://apis.google.com/js/platform.js" async defer></script>
Define your Google Sign in button:
const GoogleButton: FC<GoogleButtonProps> = ({onSuccess, onFailure}) => {
const onSignInFailure = (failure: any) => {
console.error('FAILURE: ', failure);
onFailure(...);
};
const onSignIn = (googleUser: any) => {
signInWithGoogleUser(googleUser)
.then(onSuccess)
.catch(onFailure);
}
return (
<GoogleLogin
render={(renderProps) => (
<button
onClick={renderProps.onClick}
disabled={renderProps.disabled}>
This is my custom Google button
</button>
)}
clientId={process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!}
buttonText="Login"
onSuccess={onSignIn}
onFailure={onSignInFailed}
cookiePolicy={'single_host_origin'}
prompt="select_account"
/>
);
};
I specified prompt="select_account" to be sure it prompt the user for the account selection even if hse is already logged in.
Then, inside signInWithGoogleUser, is use signInWithCredential to use the googleUser to authentify on Firebase Auth:
export const signInWithGoogleUser = (googleUser: any) => {
const auth = firebase.auth();
const provider = firebase.auth.GoogleAuthProvider;
const credential = provider.credential(googleUser.getAuthResponse().id_token);
return auth.signInWithCredential(credential);
};
NOTES
a. My project is running on Vercel [https://vercel.com/] with the Database (Firestore) and Authentication on Firebase [https://firebase.com]. You will have to adapt my solution if you use another stack.
b. With this, it works on almost all iPhones and Mac computers. In case it still fails, it is due to an installed content/cookie blocker and I display an error message asking the visitor to reactivate cookies or register using email and password.
c. I simplified a bit the code to have a more condensed answer.
I am using ReactPlaidLink to build an app that retrieves users transaction history and displays it on the front end. My goal right now is to get the webhooks to fire at the specified endpoint. I set up an endpoint to listen for requests using both webhook.site and requestbin, and have not seen a request come through. I have specified the endpoint in both the React-Plaid. From what I understand form the documentation, specifying a webhook paramater is all that's necessary for the webhooks to fire to the right endpoint. I am definitely a novice when it comes to webhooks, I hope I'm not missing something super obvious. Would appreciate any insight you can offer.
<PlaidLinkButton
buttonProps={{
className:
"btn btn-large waves-effect waves-light hoverable blue accent-3 main-btn"
}}
plaidLinkProps={{
clientName: "BankLinker",
key:" ",
token: "my-token",
env: "sandbox",
product: ["transactions"],
webhook: 'https://webhook.site/465538a7-f2a5-4d55-b017-db23b949ef91',
onSuccess: this.handleOnSuccess,
}}
onScriptLoad={() => this.setState({ loaded: true })}
>
Add Account
</PlaidLinkButton>```
and HandleSuccess
```handleOnSuccess = (token, metadata) => {
const { accounts } = this.props;
const plaidData = {
public_token: token,
metadata: metadata,
accounts: accounts,
// webhook: "https://webhook.site/465538a7-f2a5-4d55-b017-db23b949ef91",
};
this.props.addAccount(plaidData);
};
I'm not an expert on the RN SDK, but on a quick glance I don't see anything glaringly, obviously wrong here. Can you confirm that you can go through the Link flow, wait a few seconds, and then call the /transactions/get endpoint, and successfully get data back from it? That will help confirm that the issue is with webhooks and not some other part of the process.
what's wrong with this picture ?
import firebase from 'firebase';
onButtonPress() {
const { email, password } = this.state;
firebase.auth().signInWithEmailAndPassword(email, password)
.catch(() => {
firebase.auth().createUserWithEmailAndPassword(email, password)
.catch(() => {
this.setState({ error: 'Authentication Failed' });
});
});
}
And so that we're clear:
Yes it's installed in the framework when I built the app. And yes I'm calling it on the same page where this is being executed. And yes the app runs fine without this section of code. there are no coding errors, nor logic errors.
If I wanted to a.) debug this bit of code how would I do that ? and b.) where would I add the console.log statement ? I know it has to live somewhere....like here >
firebase.auth().signInWithEmailAndPassword(console.log(email, password)) ??
Shouldn't a call like these to firebase work like this ?
Thanks ahead of time.
Miles.
Oy! When I called the function to press the button I had set up. I had written onButtonPress instead of onPress....grrrrrrrrr! Sorry to bother everyone. All is well now.
You need to add firebase to your application, there are specific steps mentioned on their site. The site has information no how to call firebase for email authentication.