Skip to content

Commit 3b393aa

Browse files
fix(api): default OIDC discovery issuer to API_EXTERNAL_URL
The /.well-known/openid-configuration handler used config.JWT.Issuer unconditionally, returning an empty issuer and relative endpoint URLs for self-hosted operators who configure API_EXTERNAL_URL but leave GOTRUE_JWT_ISSUER unset. That violates OpenID Connect Discovery 1.0 section 4.2 (issuer MUST be present) and RFC 8414 section 3 (endpoint URLs MUST be absolute). Default issuer to config.API.ExternalURL when JWT.Issuer is empty; API.ExternalURL is required:"true" in the config struct so it is always populated. Also use the trailing-slash-stripped local variable when returning the Issuer field so it matches how endpoint URLs are constructed. Adds in-process tests asserting that issuer and endpoint URLs are absolute when JWT.Issuer is empty, and that a trailing slash on a configured JWT.Issuer is stripped consistently from the issuer field. Fixes #2487 Signed-off-by: Manas Srivastava <mastermanas805@gmail.com>
1 parent 7f88985 commit 3b393aa

2 files changed

Lines changed: 49 additions & 1 deletion

File tree

internal/api/jwks.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,17 @@ type OpenIDConfigurationResponse struct {
6666
func (a *API) WellKnownOpenID(w http.ResponseWriter, r *http.Request) error {
6767
config := a.config
6868
issuer := config.JWT.Issuer
69+
if issuer == "" {
70+
issuer = config.API.ExternalURL
71+
}
6972

7073
// Ensure issuer doesn't end with a slash to avoid double slashes in URLs
7174
for len(issuer) > 0 && issuer[len(issuer)-1] == '/' {
7275
issuer = issuer[:len(issuer)-1]
7376
}
7477

7578
response := OpenIDConfigurationResponse{
76-
Issuer: config.JWT.Issuer,
79+
Issuer: issuer,
7780
AuthorizationEndpoint: issuer + "/oauth/authorize",
7881
TokenEndpoint: issuer + "/oauth/token",
7982
JWKSURL: issuer + "/.well-known/jwks.json",

internal/api/jwks_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"net/http"
88
"net/http/httptest"
9+
"strings"
910
"testing"
1011

1112
"github.com/lestrrat-go/jwx/v2/jwk"
@@ -77,3 +78,47 @@ func TestJwks(t *testing.T) {
7778
})
7879
}
7980
}
81+
82+
func TestWellKnownOpenIDIssuerFallbackToExternalURL(t *testing.T) {
83+
mockAPI, _, err := setupAPIForTest()
84+
require.NoError(t, err)
85+
86+
mockAPI.config.JWT.Issuer = ""
87+
mockAPI.config.API.ExternalURL = "https://auth.example.com"
88+
89+
req := httptest.NewRequest(http.MethodGet, "/.well-known/openid-configuration", nil)
90+
w := httptest.NewRecorder()
91+
mockAPI.handler.ServeHTTP(w, req)
92+
require.Equal(t, http.StatusOK, w.Code)
93+
94+
var resp OpenIDConfigurationResponse
95+
require.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
96+
97+
require.Equal(t, "https://auth.example.com", resp.Issuer)
98+
require.True(t, strings.HasPrefix(resp.AuthorizationEndpoint, "https://auth.example.com/"), "authorization_endpoint should be absolute, got %q", resp.AuthorizationEndpoint)
99+
require.True(t, strings.HasPrefix(resp.TokenEndpoint, "https://auth.example.com/"), "token_endpoint should be absolute, got %q", resp.TokenEndpoint)
100+
require.True(t, strings.HasPrefix(resp.JWKSURL, "https://auth.example.com/"), "jwks_uri should be absolute, got %q", resp.JWKSURL)
101+
require.True(t, strings.HasPrefix(resp.UserInfoEndpoint, "https://auth.example.com/"), "userinfo_endpoint should be absolute, got %q", resp.UserInfoEndpoint)
102+
}
103+
104+
func TestWellKnownOpenIDIssuerStripsTrailingSlash(t *testing.T) {
105+
mockAPI, _, err := setupAPIForTest()
106+
require.NoError(t, err)
107+
108+
mockAPI.config.JWT.Issuer = "https://auth.example.com/"
109+
mockAPI.config.API.ExternalURL = "https://something-else.example.com"
110+
111+
req := httptest.NewRequest(http.MethodGet, "/.well-known/openid-configuration", nil)
112+
w := httptest.NewRecorder()
113+
mockAPI.handler.ServeHTTP(w, req)
114+
require.Equal(t, http.StatusOK, w.Code)
115+
116+
var resp OpenIDConfigurationResponse
117+
require.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
118+
119+
require.Equal(t, "https://auth.example.com", resp.Issuer)
120+
require.Equal(t, "https://auth.example.com/oauth/authorize", resp.AuthorizationEndpoint)
121+
require.Equal(t, "https://auth.example.com/oauth/token", resp.TokenEndpoint)
122+
require.Equal(t, "https://auth.example.com/.well-known/jwks.json", resp.JWKSURL)
123+
require.Equal(t, "https://auth.example.com/oauth/userinfo", resp.UserInfoEndpoint)
124+
}

0 commit comments

Comments
 (0)