OAuth2 Callback Error

Hi all,
I’m a newbie to Anvil, APIs, OAuth and lots of things in general, and I would greatly appreciate some assistance regarding my current dilemma.

What I’m trying to do:
I’m trying to authenticate with a third-party api using OAuth2. I found this great example of Anvil OAuth application, and I used it as my starting point:

https://anvil.works/build#clone:XQZFL5OYXDIBOA5Y=FSY65DSKQ4GMV2ME2U3LX7LK

What I’ve tried and what’s not working:
In the first step of the process, I make an api request to get the code that I need to exchange for a bearer token. I set the callback url in my third-party application and have used the @anvil.server.http_endpoint decorator to create my endpoint url. I can see in my browser that the third-party application returns the code as a parameter in the callback url and the callback url appears correct in the form of https://[my_anvil_app_url]/callback. However, instead of showing any response in the browser window, I see this error message:

I added the cross_site_session paramater in the decorator to see if it would make any difference, but it didn’t.

This is the current code in my server module:

import anvil.secrets
import anvil.server
import string
import anvil.http
import requests
import json

OAUTH_URI = "https://[third_party_host]"

CALLBACK_URI =  "https://[my_anvil_app]/callback"

@anvil.server.callable
def get_oauth_redirect_url():

  url = "{oauth_uri}/oauth/authorize?client_id={client_id}&response_type=code&redirect_uri={redirect_uri}".format(oauth_uri=OAUTH_URI,client_id=anvil.secrets.get_secret('a2j_app_key'),redirect_uri=CALLBACK_URI,)
  return url

@anvil.server.http_endpoint("/callback", cross_site_session=True)
def callback(**kws):
  
  print("code: " + kws['code'])
  
  if 'code' not in kws:
    return "Please make app public to use Auth0."
    
  url = "{oauth_uri}/oauth/token".format(oauth_uri=OAUTH_URI)
  data = {
    "grant_type": "authorization_code",
    "client_id": anvil.secrets.get_secret('a2j_app_key'),   # Provided by Auth0
    "client_secret": anvil.secrets.get_secret('a2j_app_secret'),   # Provided by Auth0
    "redirect_uri": CALLBACK_URI,
    "code": kws['code']
  }
  
  r = requests.post(url, data=data)
  r = r.json()
        
  print (r['access_token'])
  print (r['refresh_token'])

  resp = anvil.server.HttpResponse()
  resp.status = 302
  resp.headers["Location"] = anvil.server.get_app_origin()
  return resp

Hi @david2,

Welcome to the Anvil forum! All reasonable third-party OAuth2 providers will let you set the callback URL when configuring your application through their website. In this case, you have correctly written your http_endpoint with a route of "/callback", so you will need to set your OAuth2 callback URL to https://[YOUR-APP-URL]/_/api/callback.

See the documentation here for an explanation of the http_endpoint decorator, which explains how to construct the URL from the http_endpoint route.

I hope that helps!

Hi @daviesian,

Thanks so much for your reply. Unfortunately, I’m still having problems with the callback. In my third-party application, I set the redirect uri to [my_app_url]/_/api/callback. In my Anvil server module, I hard coded CALLBACK_URI to [my_app_url]/_/api/callback I return this variable in a string containing the the entire oauth request back to a custom form component located in my Main form. The component has the following show event:

  def form_show(self, **event_args):
    self.raise_event('x-redirecting')
    url = anvil.server.call('get_oauth_redirect_url')
    alert(url)
    self.call_js("redirect", url)

The js function is as follows:

<script>
  function redirect(url) {
    document.location.href = url;
  }
</script>

The problem, however, is that when running the application in production mode, the url that gets passed to the thrid-party application from the JS function is as follows:

https://app.clio.com/oauth/authorize?client_id=[app_key]&response_type=code&redirect_uri=https://[my_app_url]/callback

This is baffling to me because I construct the string in the get_oauth_redirect_url function of the server module as such:

@anvil.server.callable
def get_oauth_redirect_url():
  return (
    "{oauth_uri}/authorize?"
    "client_id={client_id}&"
    "response_type=code&"
    "redirect_uri={redirect_uri}&"
    "state=xyz").format(
      oauth_uri=OAUTH_URI,
      client_id=anvil.secrets.get_secret('a2j_app_key'),
      redirect_uri=CALLBACK_URI,
    )

I don’t understand how the url that gets passed to the third-party server doesn’t contain the full CALLBACK_URI value, nor does it contain the state parameter. :confused:

If I manually change the url redirect_uri parameter in the browser to contain "/_/api/, then the third-party application responds as expected.

Is this possibly some kind of bug, or a refresh issue of the Anvil application? I’ve tried clearing my browser cache, but that didn’t make any difference.

Anyway, I greatly appreciate any insight you (or anyone) may be able to provide. Thanks again.

Hi @david2

I’m glad you were able to demonstrate that the URL works manually. My guess is that you’re not able to test the OAuth flow in the Anvil Editor because the page is in an iframe, so you have been visiting the URL of your app to test it in a new tab. This is entirely reasonable, but have you also published an older version of your app? If so, that old version will load when you visit your app via its published URL, so you won’t see updates from your latest changes. Try publishing the latest version of your app (or unpublishing it entirely) and everything should work!

I would also recommend switching to the Beta Editor, which will let you run your app in a new tab. That way, you can debug without needing to think about publishing your app at all.

Finally, a small hint for JS integration: These days, you can redirect straight from Python using the anvil.js module. In particular, you can do something like:

from anvil.js import window

# Then, later:
def form_show(self, **event_args):
    window.location.href = anvil.server.call('get_oauth_redirect_url')

I hope this helps!

2 Likes

Hi @daviesian,

Thanks so much! You were absolutely correct. I have been trying to test my app via the url and did not publish my recent changes. Such a rookie mistake! Anyway, all is working now.

Cheers.

1 Like