ccrockett / omniauth-keycloak

Keycloak Strategy for OmniAuth.
MIT License
53 stars 42 forks source link

Issue passing params to callback URL #44

Open LaurelOlson opened 10 months ago

LaurelOlson commented 10 months ago

I'm using omniauth-keycloak with devise in a rails + react project and I have keycloak omniauth hooked up successfully for regular login/signup. I'm trying to add it to the devise invitation flow and am running into an issue passing the invitation_token so it is accessible in the omniauth callback endpoint.

I added the param into authorize_options and passed it as a hidden input in the form as per #24 but this doesn't seem to do anything to the callback request. I'm not sure if I'm misunderstanding the feature or if there is something wrong with the config.

I also tried passing the param directly in the form action e.g. action={'/auth/keycloak?invitation_token=${token}'} but this causes an Incorrect redirect_uri error in the callback phase, presumably due to the invitation_token in the query params. The keycloak integration redirect uri is set to http://localhost:3000/*

Relevant code:

# config/initializers/devise.rb
config.omniauth :keycloak_openid,
                  ENV["KEYCLOAK_CLIENT"],
                  ENV["KEYCLOAK_SECRET"],
                  client_options: {
                    site: ENV["KEYCLOAK_AUTH_URL"],
                    realm: "standard",
                  },
                  authorize_options: [:invitation_token],
                  name: :keycloak,
                  strategy_class: OmniAuth::Strategies::KeycloakOpenId
// form.tsx
<form action={`/api/auth/keycloak`} method="post">
    <Input hidden={true} name="invitation_token" value={invitationToken} />
    <input
        type="hidden"
        name="authenticity_token"
        value={document.querySelector("[name=csrf-token]").content}
    />
    <Button type="submit">Login</Button>
</form>
ushmakapure commented 8 months ago

Keycloak wants the request phase and the callback phase redirect_uri to match exactly. When you call '/auth/keycloak?invitation_token=${token}' I believe Omniauth appends some state and session data to the callback phase (tbh not quite sure where this comes from). To get around this, the gem overrides build_access_token to edit the callback url to strip out all query params. https://github.com/ccrockett/omniauth-keycloak/blob/7322bf13159867eeb610e0b5efe86083253eb1e9/lib/omniauth/strategies/keycloak-openid.rb#L96

So ultimately, the request phase redirect_uri will include the invitation_token you've passed in from the client, but the callback phase will strip all params, resulting in mismatched redirect_uris and ultimately login failure.

I adapted the gem and ended up implementing something like this to allow the params to be passed through

def build_access_token
        verifier = request.params['code']

        client.auth_code.get_token(verifier,
                                  { redirect_uri: remove_extra_params(request.params, callback_url) }
                                  .merge(token_params.to_hash(symbolize_keys: true)),
                                  deep_symbolize(options.auth_token_params))
 end

# Strips unwanted query params from urls
def remove_extra_params(params, url)
      ignored_params = %w[state session_state iss code]
      accepted_params = params.reject { |k, _v| ignored_params.include?(k) }

      raw_query_string = ''
      accepted_params.each { |k, v| raw_query_string += "#{k}=#{v}&" }
      query = raw_query_string.delete_suffix('&')

      stripped_url = url.gsub(/\?.+\Z/, '')
      query.blank? ? stripped_url : "#{stripped_url}?#{query}"
end