Bring Drag & Drop to Dash with Dashboard Engine. 💫 Learn how at our next webinar!

Integrating Google's reCAPTCHA with Dash

Hi all,

I recently had to figure out how to integrate reCAPTCHA V2 with Dash and there were no resources online on how to do it, so I thought I would share it here.

Typically, you would do this by including a div element in your form with class=“g-recaptcha” and a script with source src=“https://www.google.com/recaptcha/api.js

The problem I found is that Dash renders the html elements after the script runs, so the captcha does not render.

This is how I managed to do it:

First, I will assume you have registered your site (reCAPTCHA) and have received a sitekey and secret. I store these in app config:

app.server.config['RECAPTCHA_SITEKEY'] = ...
app.server.config['RECAPTCHA_SECRET'] = ...

We need to add the google reCAPTCHA script when initialising dash.

app = dash.Dash(__name__,external_scripts = ['https://www.google.com/recaptcha/api.js?render=explicit'])

Note the “render=explicit” in query string. This means we are going to render the captcha it explicitly though JavaScript.

In your layout you can create 3 html components:

html.Div(id='login-recaptcha')
html.Div(id='login-recaptcha-response',style={'display':'none'})
html.Button('Log In', id='login-button', n_clicks=0)

Now we add a client side callback:

app.clientside_callback(
        """
        function(n_clicks) {
            if (n_clicks==0){
                grecaptcha.render('login-recaptcha', {'sitekey' : '_sitekey'});
            }
            if (n_clicks > 0){
                var recaptcha_response = document.getElementById('g-recaptcha-response');
                return recaptcha_response.value;
            }
        }
        """.replace('_sitekey',app.server.config['RECAPTCHA_SITEKEY']),
        
        Output('login-recaptcha-response', 'children'), 
        Input('login-button', 'n_clicks')
    )

What this does is as follows:

if n_clicks==0 then we render the reCAPTCHA with grecaptcha (this will happen after the page loads)

The response of the user is stored in a hidden textarea with id ‘g-recaptcha-response’. The problem is that we cannot link this to a callback because it is injected by calling grecaptcha.render after the page loads. Therefore, we have to transfer its contents to another html component that we can link to a callback. That’s what happens in the next section of the code. If n_clicks > 0 we copy the user’s response to our ‘login-recaptcha-response’ div.

Now we are ready to handle the response on the server:

@app.callback(
        Output('request-url','pathname'),
        Input('login-button','n_clicks'),
        Input('login-recaptcha-response','children'),
    )
    def handle_login(nclicks,recaptcha_response):

          if recaptcha_response is None or recaptcha_response.strip() == '':
                        raise PreventUpdate

          request_headers = {"Content-Type": "application/x-www-form-urlencoded; charset=utf-8" }

          request_url = 'https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}'.format(app.server.config['RECAPTCHA_SECRET'],recaptcha_response)
          response = requests.post(request_url,headers=request_headers)

          response_json = json.loads(response.text)    
       
          if response_json['success'] == False:
             raise PreventUpdate
        
       # do what you want here after the captcha has been verified.

And that’s it! Hope it helps and if someone finds an easier way to do it do let me know.

Regards,
Ofir

4 Likes

This is a great solution! And actually a generally useful technique for including DOM-dependent JS in Dash (which is currently a problem without a first class solution)