Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keycloak Login results in Error: "Unable to exchange external code" #516

Closed
valerius21 opened this issue Jun 21, 2022 · 10 comments
Closed
Labels
bug Something isn't working

Comments

@valerius21
Copy link

valerius21 commented Jun 21, 2022

Bug report

Describe the bug

After setting up an instance of Keycloak and using current examples with their latest versions, logging in via. the thrid-party-service Keycloak on a local instance results in an error, which message is "Unable to exchange external code", found in the redirect URL.

To Reproduce

  1. Follow the documentation on setting up Keycloak for Supabase, https://supabase.com/docs/guides/auth/auth-keycloak.
  2. Initialize a new project with npx create-next-app --example "https://github.com/valerius21/auth-helpers/tree/main/examples/nextjs"
    1. Enter the corresponding credentials in a .env.local file (NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY)
    2. Change pages/index.tsx:38 from providers={["google", "github"]} to providers={["google", "github", "keycloak"]}
    3. Start the Dev Server with yarn dev
    4. (This Step is also reproducible with the official supabase-js client)
  3. The site at http://localhost:3000 should have three buttons, one of which has no icon inside. image
  4. Click the button and login on to the local Keycloak instance
  5. Redirected back to the authentication form, no session is established and an error code in the URL is present.
http://localhost:3000/?error=server_error&error_description=Unable+to+exchange+external+code%3A+392cabd5-b422-446a-8497-e4f184453157.0887172c-5c95-44a4-944b-d975911a9563.46e8656a-f404-41c8-ab90-baa7ffb0d727

Expected behavior

A user session, similar to other OAuth2 providers, like Google, GitHub, etc., where querying DB content and one's user profile is possible.

Screenshots

supabase_issue.mp4

System information

  • OS: Ubuntu Linux
  • Browser: Chrome, Brave
  • Version of supabase-js: latest
  • Version of Node.js: 16.15.x

Previous attempts to resolve the issue

  • Checking the Keycloak credentials of whitespaces
  • Adding SSL did not resolve the issue
  • Inspecting the supabase instance logs of errors, but only, as intended, finding 302 requests.
  • Using the supabase client in other frameworks and in other contexts to attempt an authentication request.
@valerius21
Copy link
Author

Doing some more inspection, the Auth Logs show the following error:

time="2022-06-21T09:23:11Z" level=error msg="500: Unable to exchange external code: e3e63f45-6be0-4b58-a4ab-180bcd28f0b9.02187b20-0342-4ac6-b341-e218ba88dc74.46e8656a-f404-41c8-ab90-baa7ffb0d727" component=api error="Post \"http://localhost:8081/auth/realms/kind/protocol/openid-connect/token\": dial tcp 127.0.0.1:8081: connect: connection refused" method=GET path=/callback referer= remote_addr="95.90.186.196:35661" request_id=63b28020-76a1-4047-98d6-afc06994e40d
[
  {
    "_LINE_BREAK": null,
    "_EXE": null,
    "status": null,
    "_MACHINE_ID": null,
    "SYSLOG_FACILITY": null,
    "request_id": null,
    "project": "mrkpghsoraiqyrxtoqkl",
    "_UID": null,
    "remote_addr": null,
    "_CAP_EFFECTIVE": null,
    "_PID": null,
    "SYSLOG_IDENTIFIER": null,
    "__REALTIME_TIMESTAMP": null,
    "_TRANSPORT": null,
    "_COMM": null,
    "CODE_FUNC": null,
    "_SYSTEMD_UNIT": null,
    "__MONOTONIC_TIMESTAMP": null,
    "_STREAM_ID": null,
    "host": "mrkpghsoraiqyrxtoqkl",
    "source_type": "journald",
    "UNIT": null,
    "_BOOT_ID": null,
    "_SYSTEMD_CGROUP": null,
    "MESSAGE_ID": null,
    "_SYSTEMD_SLICE": null,
    "CODE_LINE": null,
    "EXECUTABLE": null,
    "level": null,
    "_SYSTEMD_INVOCATION_ID": null,
    "INVOCATION_ID": null,
    "referer": null,
    "path": null,
    "CODE_FILE": null,
    "_GID": null,
    "PRIORITY": null,
    "method": null,
    "_SOURCE_REALTIME_TIMESTAMP": null,
    "duration": null,
    "_CMDLINE": null,
    "component": null,
    "_SELINUX_CONTEXT": null
  }
]

@valerius21
Copy link
Author

After further inspection, I've tried to use a self-hosted instance of supabase. The error disappears; Keycloak sets a session and I'll get redirected to the origin. However, a session cookie is not set. The response from Keycloak looks like this:

URL after redirect

http://localhost:3001/?state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTU4MTk4ODQsInNpdGVfdXJsIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwiaWQiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAiLCJuZXRsaWZ5X2lkIjoiIiwiZnVuY3Rpb25faG9va3MiOm51bGwsInByb3ZpZGVyIjoia2V5Y2xvYWsiLCJyZWZlcnJlciI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMS8ifQ.u7kyq_uhqp9ERkV_-kF1Qsv8E_SWoWidVJHyogTMBgY&session_state=04f353f9-0850-4544-bd1b-fca8d3735cd8&code=f51f1ec0-a382-4655-abaf-f9c1e07dee36.04f353f9-0850-4544-bd1b-fca8d3735cd8.27e32ec1-4723-43ec-a076-6e7ffa018c47

@kangmingtay kangmingtay transferred this issue from supabase/supabase Jun 27, 2022
@koakh
Copy link

koakh commented Jul 6, 2022

Hello
I have the same problem using app.supabase.com

redirect url with failure "Unable to exchange external code"

http://localhost:3000/?error=server_error&error_description=Unable+to+exchange+external+code%3A+a95bcaef-bb46-4c04-9ec6-2a81877a68f0.be71884e-2598-4dea-9d0f-61766b30e403.dfae8b4b-e82d-466c-ac57-d112d9b92d7c

another unrelated strange thing is
when I use a self hosted supabase using this guide Self Hosting With Docker
I don't see the auth providers, its like a incomplete supabase, without many functions, like oauth2 auth providers
we can see it in bellow image

image

How can I use self hosted supabase with oauth2 providers to test it with keycloak, like @valerius21

@valerius21
Copy link
Author

valerius21 commented Jul 8, 2022

@koakh When starting a local instance, add the following lines here to your docker-compose.yml

      GOTRUE_EXTERNAL_KEYCLOAK_ENABLED: "true"
      GOTRUE_EXTERNAL_KEYCLOAK_CLIENT_ID: "<client id>"
      GOTRUE_EXTERNAL_KEYCLOAK_SECRET: "<client secret>"
      GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI: "http://localhost:<PORT of your webapp>/"
      GOTRUE_EXTERNAL_KEYCLOAK_URL: "http://<keycloak instance>/auth/realms/<realm>"

@koakh
Copy link

koakh commented Jul 8, 2022

I will try it later (asap) and update my reply with feedback, maybe will be useful for others

thanks @valerius21

update #1

testing with both supabase hosted and supabase self host with docker instructions here https://supabase.com/docs/guides/hosting/docker

and suggested env

GOTRUE_EXTERNAL_KEYCLOAK_ENABLED: "true"
GOTRUE_EXTERNAL_KEYCLOAK_CLIENT_ID: "<client id>"
GOTRUE_EXTERNAL_KEYCLOAK_SECRET: "<client secret>"
GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI: "http://localhost:<PORT of your webapp>/"
GOTRUE_EXTERNAL_KEYCLOAK_URL: "http://<keycloak instance>/auth/realms/<realm>"

ex

GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI: "http://localhost:8000/auth/v1/callback"
GOTRUE_EXTERNAL_KEYCLOAK_URL: "http://localhost:8080/auth/realms/SupaBase"

first error

beware one of the problems that I have was the docker-compose.yml have a old version of gotrue without keycloak provider

in docker-compose.yml we have supabase/gotrue:v2.5.21 we need to change supabase/gotrue:v2.7.2 or supabase/gotrue:latest in this case is the same version
with that, we fix the problem of missing keycloak keycloak provider

lets focus in the super annoying problem with hosted and self hosted error_description | Unable to exchange external code: 38891947-e2c2-4a03-ad26-6f25e9b51e9d.ea289a44-b80e-4c24-b48c-1d0747266cfe.234c8eb6-706a-4083-814e-56a1a514e557

I try with both images and is the same

  • quay.io/keycloak/keycloak:legacy
  • jboss/keycloak:latest

update #2 : using keycloak in supabase docker stack and start debug

# check gotrue logs
$ docker-compose logs -f auth

supabase-auth | time="2022-07-09T00:15:25Z" level=info msg="request started" component=api method=GET path=/authorize referer="http://localhost:3030/" remote_addr="192.168.96.1:45942" request_id=39397c7b-01e3-4dbb-919a-191c0c5dce8c
supabase-auth | time="2022-07-09T00:15:25Z" level=info msg="Redirecting to external provider" component=api method=GET path=/authorize provider=keycloak referer="http://localhost:3030/" remote_addr="192.168.96.1:45942" request_id=39397c7b-01e3-4dbb-919a-191c0c5dce8c
supabase-auth | time="2022-07-09T00:15:25Z" level=info msg="request completed" component=api duration=159963 method=GET path=/authorize referer="http://localhost:3030/" remote_addr="192.168.96.1:45942" request_id=39397c7b-01e3-4dbb-919a-191c0c5dce8c status=302
supabase-auth | time="2022-07-09T00:15:25Z" level=info msg="request started" component=api method=GET path=/callback referer= remote_addr="192.168.96.1:45942" request_id=4428c95b-3513-494f-82ab-c6b1c554b31b
supabase-auth | time="2022-07-09T00:15:25Z" level=error msg="500: Unable to exchange external code: dd4c2c81-f956-40a3-860a-764eaede02fb.fa0ff490-0ea9-4277-bf20-df339d7ec938.8a75907e-7b0d-4a2a-87ef-68c46447a957" component=api error="Post \"http://localhost:8080/auth/realms/SupaBase/protocol/openid-connect/token\": dial tcp 127.0.0.1:8080: connect: connection refused" method=GET path=/callback referer= remote_addr="192.168.96.1:45942" request_id=4428c95b-3513-494f-82ab-c6b1c554b31b
supabase-auth | time="2022-07-09T00:15:25Z" level=info msg="request completed" component=api duration=5801455 method=GET path=/callback referer= remote_addr="192.168.96.1:45942" request_id=4428c95b-3513-494f-82ab-c6b1c554b31b status=302

seems clear that the error is here api error="Post \"http://localhost:8080/auth/realms/SupaBase/protocol/openid-connect/token\": dial tcp 127.0.0.1:8080: connect: connection refused" related to GOTRUE_EXTERNAL_KEYCLOAK_URL`

# test keycloak endpoint
$ curl -X POST http://localhost:8080/auth/realms/SupaBase/protocol/openid-connect/token
{"error":"invalid_request","error_description":"Missing form parameter: grant_type"}

enter inside gotrue container we can see that we can't have connection in localhost:8080 and 127.0.0.1:8080 only with keycloak:8080 works

$ docker exec -it supabase-auth sh

/ $ wget localhost:8080
Connecting to localhost:8080 (127.0.0.1:8080)
wget: can't connect to remote host (127.0.0.1): Connection refused

/ $ wget 127.0.0.1:8080
Connecting to 127.0.0.1:8080 (127.0.0.1:8080)
wget: can't connect to remote host (127.0.0.1): Connection refused

/ $ wget keycloak:8080
Connecting to keycloak:8080 (192.168.96.6:8080)
# works here
wget: can't open 'index.html': Permission denied

the question is why gotrue can connect to keycloak ?
answear is because we cant connect to localhost:8080, localhost is the gotrue container and there is no 8080 service running on gotrue container, this 8080 belongs to keycloak,
to override this we can use keycloak hostname ex keycloak:8080, and add to host file 127.0.0.1 keycloak, to test frontend work, ok it works

update #3: at last I find how to put it to work after more 4h digging in the shit hole
It work flawless with nextjs frontend

the trick for inter container communication is using

  1. keycloak:8080
    (required add to host files 127.0.0.1 keycloak, just for test purposes)
GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI: "http://localhost:8000/auth/v1/callback"
GOTRUE_EXTERNAL_KEYCLOAK_URL: "http://keycloak:8080/auth/realms/SupaBase"
  1. docker host ip (just for test purposes)
ip addr show docker0 | grep -Po 'inet \K[\d.]+'
172.17.0.1
GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI: "http://localhost:8000/auth/v1/callback"
GOTRUE_EXTERNAL_KEYCLOAK_URL: "http://172.17.0.1:8080/auth/realms/SupaBase"
  1. public domain (production)
GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI: "http://localhost:8000/auth/v1/callback"
GOTRUE_EXTERNAL_KEYCLOAK_URL: "https://keycloak.mydomain.com/auth/realms/SupaBase"

keycloak.mydomain.com is a ronline domain with everse proxy behind caddy with http/tls

in keycloak supabase client Valid Redirect URIs
(same working for hosted and self hosted, and for local self hosted (localhost) and with my public doamain)

image

Keycloak URL for hosted version: https://keycloak.mydomain.com/auth/realms/SupaBase

other tricks than may trigger the error "Unable to exchange external code"

  1. keycloak users required valid mail, and that mail must be verified

2 .(self hosted only with smtp enabled and connection problems like inside gotrue try to connect to port 53/smtp, same inter container problem)
ENABLE_EMAIL_AUTOCONFIRM=true

now I have local and online nextjs, supabase and keycloak working, if anyone needs help feel free to ask

thanks

PS: in my opinion https://supabase.com/docs/guides/auth/auth-keycloak don't help when it says

quote="Obtain the issuer from the "OpenID Endpoint Configuration". This will be used as the Keycloak URL."

depends if its from a localhost don't help, only create confusion, how supabase hosted and self hosted deals with localhost:8080? yes don't deal.....even if all services are running in a local machine with docker, I don't try running binaries in host machine but that will work because gotrue will reach localhost:8080

in that link maybe a note saying that the service must a public ip or domain with port 8080 exposing keycloak service helps

@valerius21
Copy link
Author

@koakh, thank you very much for your write-up! I'll try to replicate everything once I find time to do it. IMO the documentation needs some updating before someone gets into the rabbit hole of trying to find out how it works.

@hf
Copy link
Contributor

hf commented Sep 28, 2022

Thank you @koakh for the write up. I'll try to summarize it for others.

When running both Keycloak and GoTrue locally, you need to make sure that both of them can actually talk to each other over the operating system's network layer. There is no one way to fix the problem, depending on how you have set up the Keycloak or GoTrue instance to run.

Note that typically things running within a docker container can't talk to things running outside of it, especially on localhost. I believe this to be the cause of the errors with the form of:

Post \"http://localhost:8080/auth/realms/SupaBase/protocol/openid-connect/token\": dial tcp 127.0.0.1:8080: connect: connection refused

It may be just easier to host Keycloak on a public web server with a correct URL to avoid dealing with complex docker networking issues especially if you don't have sufficient knowledge or experience in dealing with them.

@hf hf closed this as not planned Won't fix, can't repro, duplicate, stale Sep 28, 2022
@olee
Copy link

olee commented Aug 2, 2023

@koakh Hey, I'm having the same issue with auth not being able to reach keycloak because it is running in another container:

{
    "component": "api",
    "error": "Post \"http://localhost:8085/realms/app/protocol/openid-connect/token\": dial tcp 127.0.0.1:8085: connect: connection refused",
    "level": "error",
    "method": "GET",
    "msg": "500: Unable to exchange external code: 330d1058-fb86-45e7-9f7a-609276ee801e.daeeb333-2aa6-440f-9598-4bbf132a530f.bda8d7d3-8395-4429-aed6-74e07b7efd64",
    "path": "/callback"
}

How did you solve this issue in the end then? When I set GOTRUE_EXTERNAL_KEYCLOAK_URL: http://keycloak:8085/realms/app the redirect to keycloak in the browser just gets broken.


EDIT: For anyone else looking for a solution on how to get this running when both containers are running on the same machine on windows, I found a solution:

  • Add an env var with your machine's LAN IP address like KEYCLOAK_HOSTNAME=192.168.178.60
  • Set Keycloak hostname to this address KC_HOSTNAME: ${KEYCLOAK_HOSTNAME}
  • Set Keycloak URL for gotrue also to this address GOTRUE_EXTERNAL_KEYCLOAK_URL: 'http://${KEYCLOAK_HOSTNAME}:8080/realms/${KEYCLOAK_REALM}'
  • Finally, you will have to add a port forwarding from your windows host to the wsl container
    • First, get the IP address of your wsl machine by entering a shell (eg. for me it's podman so I used wsl -d podman-machine-default)
    • Run ifconfig to get the address of the wsl machine
    • In windows, run netsh interface portproxy add v4tov4 listenport=8085 listenaddress=0.0.0.0 connectport=8085 connectaddress=<WSL-IP-ADDRESS> to add a port forwarding

After doing these steps, the authentication process worked for me 👍

@koakh
Copy link

koakh commented Aug 3, 2023

Hello @olee

Sorry for late answear,
Currently I'm not at home, and I don't remember the solution from my memory right now
Let me see it in my project, and I update here asap

UPDATE:

my lines of docker-compose.yml > read comment

keyclock env vars

  auth:
    container_name: supabase-auth
    ...
    image: koakh-supabase-gotrue:latest
    environment:
      ...
      # keycloak
      GOTRUE_EXTERNAL_KEYCLOAK_ENABLED: "true"
      GOTRUE_EXTERNAL_KEYCLOAK_CLIENT_ID: "supabase"
      GOTRUE_EXTERNAL_KEYCLOAK_SECRET: "sgTbz86zgZgdL8x7YtPzthZUlkDo0Wup"
      GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI: "http://localhost:8000/auth/v1/callback"
      # this url must be accessed from client/browser url, use docker or a online public ip
      # local: ip addr show docker0 | grep -Po 'inet \K[\d.]+' or use keycloak in url but required add to hosts `172.17.0.1 keycloak`, 172.17.0.1 works without need to add to hosts
      GOTRUE_EXTERNAL_KEYCLOAK_URL: "http://172.17.0.1:8080/auth/realms/SupaBase"

the trick is using the docker0 network ip ex 172.17.0.1

please let me know if it works for you buddie

@olee
Copy link

olee commented Aug 3, 2023

I already found a quite simlar solution based on what you had and added it to my comment above.
For me it was slightly different, because I was executing the whole thing on windows.

Thanks anyways and also for all the investigation you did above which helped me find the solution 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants