Can I expose a route with no authentication?

Is there any way I can create a route that bypasses authentication.

This is used to integrate with 3rd party services.
The requirement is providing the service an URL (headers or payloads of any type are not supported).
Previously this was possible by appending “?api_key=…&api_username=” but that is now gone.

The discourse instance is hosted by us.

3 Likes

You would need a custom plugin that creates a route that can accept a payload and act on it. Do be aware since you’re not able to authenticate the user, you may need a way to authenticate the payload.

3 Likes

My rule is no anonymous accounts. So i cannot access the plugin routes. What I would need is a way for a route to be created for this type of scenario.

For example,

  # Default Rails 3.2 lets the request through with a blank session
  # we are being more pedantic here and nulling session / current_user
  #  and then raising a CSRF exception
  def handle_unverified_request
    # NOTE: API key is secret, having it invalidates the need for a CSRF token
    unless is_api? || is_user_api?
      super
      clear_current_user
      render plain: "[\"BAD CSRF\"]", status: 403
    end
  end

A way to notify during this flow or the whole flow of authentication that a controller and an action received via params (set automatically when you call a route) can bypass everything and execute the route.

I’m not fully aware of how everything works, but since there is an anonymous access option, I’m sure there is a way to do it.

This would allow for people to encrypt the payload if needed, but for generic information updating the system, it would be useful.

1 Like

Via a plugin you can add your new route to the DiscoursePluginRegistry.api_parameter_routes so that you can continue to pass in api credentials via query parameters:

https://github.com/discourse/discourse/blob/master/lib/auth/default_current_user_provider.rb#L363

4 Likes

You’re also going to need the stanza

skip_before_action :redirect_to_login_if_required

in your custom Controller, if this is a login_required site.

2 Likes

Ok. So override that method and skip action. I will try and let you guys know if i succeed.

1 Like
# By default we only allow headers for sending API credentials
  # However, in some scenarios it is essential to send them via url parameters
  # so we need to add some exceptions
  def api_parameter_allowed?
    request_method = @env["REQUEST_METHOD"]&.downcase&.to_sym
    request_format = @env['action_dispatch.request.formats']&.first&.symbol

    path_params = @env['action_dispatch.request.path_parameters']
    request_route = "#{path_params[:controller]}##{path_params[:action]}" if path_params

    if 'm_exposed#endpoint' == request_route && request_method == 'get'
      puts 'Allowed'
      return true
    end

    PARAMETER_API_PATTERNS.any? do |p|
      (p[:method] == "*" || Array(p[:method]).include?(request_method)) &&
      (p[:format] == "*" || Array(p[:format]).include?(request_format)) &&
      (p[:route] == "*" || Array(p[:route]).include?(request_route))
    end
  end

I did this just to minimize the git diffs for future.

Also, my controller:

# frozen_string_literal: true

class MExposedController < ::ApplicationController

  skip_before_action :redirect_to_login_if_required, raise: false

  def endpoint
    render json: { state: "hello world" }
  end

end

So now the endpoint does not check params or headers credentials.

How do I make it so it checks the api key provided and only allows access if it is valid? (Like it used to work)

@riking @blake ?

I believe adding requires_login to your controller will do the trick. Have a look at some of the other controllers in app/controllers for examples.

2 Likes

Well that didn’t work :slight_smile:

class MExposedController < ::ApplicationController

  requires_login

  skip_before_action :redirect_to_login_if_required, raise: false

Completed 403 Forbidden

{"errors":["You need to be logged in to do that."],"error_type":"not_logged_in"}

This is declared inside of a plugin.

And you passed in your api credentials via query params? If so, that means your route probably didn’t get added correctly to the bypass for allowing api creds in query params. You may need to debug the bypass logic and see if your route was ever added.

Well it works and bypasses everything without requires_login. I posted how i did it. Hmm…

It seems I can only make it work without crendentials. Well at least i solved my initial problem.