auth0 / omniauth-auth0

OmniAuth strategy to login with Auth0
MIT License
125 stars 67 forks source link

CSRF detected #49

Closed rjocoleman closed 6 years ago

rjocoleman commented 7 years ago

Using omniauth-auth0 v2.0.0 but otherwise following the Rails 5 guides in the docs leads to a csrf_detected error coming out of omniauth.

provider_ignores_state = true used to be set in the provider by default. This was removed in v2.0.0. Setting this explicitly avoids the CSRF detected error but it doesn't seem like a good idea.

Is there another suggested implementation to avoid setting provider_ignores_state = true?

pouellet commented 7 years ago

This might or not applied to you, but we ran in a similar problem using omniauth-auth0 and the devise omniauth integration. In our case, we manually generated the authorize url instead of relying on the omniauth route helper (user_auth0_omniauth_authorize_path). Omniauth is taking care of generating a state query parameter, storing it in the session and comparing when the request comes back.

You will want to make sure that this method https://github.com/intridea/omniauth-oauth2/blob/master/lib/omniauth/strategies/oauth2.rb#L51 gets called as part of the request generation.

MattFenelon commented 7 years ago

I'm having the same problem. It seems that the Auth0 Lock dialog is broken with this version of the gem. It works if I do what the README says and just redirect the user to /auth/auth0, which uses the hosted Login page.

As far as I can tell, OmniAuth-OAuth2 is expecting the callback URL to include a state query-string parameter, but it doesn't. I am still trying to track down why that is. Any help appreciated.

pouellet commented 7 years ago

Auth0 will return the state value if you pass it as a parameter when you send the user to the Auth0 authentication page. For it to work, the authorize request should look something like:

https://<account>.auth0.com/authorize?auth0Client=<auth0Client>&client_id=<clientId>&redirect_uri=<callback_uri>&response_type=code&state=<uuid>

callback_uri => https://<host>/users/auth/auth0/callback

If you use the user_auth0_omniauth_authorize_path helper, it will automatically generate the state query parameter and save it in the session so that it can be validated when the callback is called.

In our case, it looks something like this:

  1. Have your login button link to https://<host>/login and have this hit an AuthenticationController in the Rails app (in our case, we put it in the same controller that handles the auth0 callback).
  2. In the controller action, redirect the user to user_auth0_omniauth_authorize_path. At this point, OmniAuth-OAuth2 generates the Auth0 authorize url, including a unique state query parameter that it store in the session.
  3. The browser follows the redirect, the user authenticate with Auth0 and Auth0 redirects to your callback url and includes the state that you passed it.
  4. OmniAuth-OAuth2 validates that the state returned in the callback equals what is stored in the session.
class AuthenticationController < Devise::OmniauthCallbacksController

  def login
    redirect user_auth0_omniauth_authorize_path
  end

   ...
end

I hope it helps.

rjocoleman commented 7 years ago

@pouellet thanks so much, that really did help! I'm now provider_ignores_state = false 👍

MattFenelon commented 7 years ago

Thanks @pouellet. That seems to be the same flow as linking the user to /auth/auth0? I am trying to use this gem with Auth0's Lock widget.

@rjocoleman mentioned using provider_ignores_state = false. I'm still not convinced this is a safe option to use - any malicious site can form a valid request to Auth0 and authenticate a user against the origin site.

That said, I'm not sure if this gem is where this problem needs to be tackled. Perhaps provider_ignores_state = false should be set, to turn off omniauth's csrf protection, and then a custom CSRF solution put in place, that adds a CSRF protection token into the state param of the Lock widget.

rjocoleman commented 7 years ago

@MattFenelon I was using lock.js, but I took it out and started using the hosted login page.

Take a look at both the authorize_params (https://github.com/intridea/omniauth-oauth2/blob/master/lib/omniauth/strategies/oauth2.rb#L51-L60 and https://github.com/auth0/omniauth-auth0/blob/master/lib/omniauth/strategies/auth0.rb#L53-L57) methods, make sure it's called and add a param state to your lockjs redirectUrl.

hoverlover commented 7 years ago

This is not working for me. I pass a state param to the auth0 authorization URL, but it doesn't pass it back. The only param I get is code. Is there a setting in my auth0 account that's preventing it from being sent back?

I'm using lock-passwordless-2.2.min.js on the Auth0 hosted login page, btw. Here's the relevant script part:

<script>
    // Decode utf8 characters properly
    var config = JSON.parse(decodeURIComponent(escape(window.atob('@@config@@'))));
    config.extraParams = config.extraParams || {};
    config.responseType = config.extraParams.response_type;
    config.dict = {
      email: {
        headerText: "Enter your email to sign in"
      },
      title: "My Title"
    };
    config.closeable = false;
    var connection = config.connection;
    var prompt = config.prompt;

    var loginHint = config.extraParams.login_hint;

    var lock = new Auth0LockPasswordless('key', 'my-domain.auth0.com');
    lock.emailcode(config);
  </script>
hoverlover commented 7 years ago

OK, I figured this out. When passing a state parameter to the auth0 hosted login page, it gets put in the extraParams object after parsing the @@config@@. I simply added an authParams object to the lock config, including a state key with the value set to config.extraParams.state.

Here's the updated script example:

<script>
    // Decode utf8 characters properly
    var config = JSON.parse(decodeURIComponent(escape(window.atob('@@config@@'))));
    config.extraParams = config.extraParams || {};
    config.responseType = config.extraParams.response_type;
    config.dict = {
      email: {
        headerText: "Enter your email to sign in"
      },
      title: "My Title"
    };
    config.closeable = false;
    config.authParams = {
        scope: 'openid profile',  // Learn about scopes: https://auth0.com/docs/scopes
        state: config.extraParams.state
      };

    var connection = config.connection;
    var prompt = config.prompt;

    var loginHint = config.extraParams.login_hint;

    var lock = new Auth0LockPasswordless('key', 'my-domain.auth0.com');
    lock.emailcode(config);
  </script>

Maybe someone from the Auth0 team can comment on why the state url param is stuffed into an extraParams object.

atwoodjw commented 7 years ago

I got CSRF working w/ Lock.

omniauth (1.6.1) omniauth-auth0 (2.0.0) omniauth-oauth2 (1.4.0) lock.min.js (10.9.1)

session_helper.rb
module SessionHelper
  def state_meta_tag
    state = SecureRandom.hex(24)
    session['omniauth.state'] = state

    tag('meta', name: 'state', content: state)
  end
end
application.html.erb
<%= csrf_meta_tags %>
<%= state_meta_tag %>
session.js.erb
var options = {
  auth: {
    redirectUrl: '<%= Rails.application.routes.url_helpers.auth_callback_url(:oauth2) %>',
    responseType: 'code',
    params: {
      scope: 'openid email',
      state: $('meta[name="state"]').attr('content')
    }
  },
  theme: {
    primaryColor: '#EA5A52'
  }
};

var lock = new Auth0Lock('<%= ENV['AUTH0_CLIENT_ID'] %>', '<%= ENV['AUTH0_DOMAIN'] %>', options);
lock.show();
ClaudioFloreani commented 7 years ago

@atwoodjw can you specify which version of the omniauth gems? (omniauth, omniauth-auth0, omniauth-oauth2)

The problem arise only when switching to omniauth-auth0 version 2.0.0, so at least some upgrading instructions should be provided for customers going to the next major release.

atwoodjw commented 7 years ago

Sure.

omniauth (1.6.1) omniauth-auth0 (2.0.0) omniauth-oauth2 (1.4.0) lock.min.js (10.9.1)

Yes, the issue only arrises in omniauth-auth0 (2.0.0) because provider_ignores_state = true is no longer set by default. CSRF protection wasn't enable by default in earlier versions.

errfanwadia commented 7 years ago

Above @atwoodjw worked like a charm.

But I am not able to achieve this for IdP initiated SSO as the IdP first authenticate and the callback URL is called directly from auth0. There is no way that you can store any state param in session as the request is initiated from IdP. Request to suggest any best practices

dguettler commented 6 years ago

@errfanwadia I ran into similar issue when doing impersonation. Everything happens on the Auth0 side before the callback to omniauth. The only solution I'm aware of so far was to disable the state check

cocojoe commented 6 years ago

@joshcanhelp Needs sorted.

cocojoe commented 6 years ago

Sorry for the long delay, picking up on this repo now. We will be going through some updates soon.

joshcanhelp commented 6 years ago

Chiming in here since the fixes are using old versions of our libraries that should be updated. Again, apologies for the long delay in response here.

See my comment here for an example of how to use this library with Lock. Make sure you're using the latest major/minor version of Lock, preferably the latest (11.7.x as of this writing). This library includes Passwordless now as well, no need for the separate library.

That said ... you're best off using the universal login page (redirecting to /authorize) as described in our Rails quickstart.

Thank you @atwoodjw for the example posted!

@hoverlover - those are parameters sent to the /authorize endpoint, hence the naming. See the example above or mine linked here as that naming has changed a bit (auth.params.state).

@errfanwadia - If you're still struggling with this, please open a new issue with information about your setup so we can troubleshoot that.

Cruz-glitch commented 3 years ago

https:///users/auth/auth0/callback

Cruz-glitch commented 3 years ago

class AuthenticationController < Devise::OmniauthCallbacksController

def login redirect user_auth0_omniauth_authorize_path end

... end

2xUPe commented 3 years ago

class AuthenticationController < Devise::OmniauthCallbacksController

def login redirect user_auth0_omniauth_authorize_path end

... end

Today I tried to sign in to https://code.videolan.org by using github.com. I got a pin , but it failed with CSRF detected. What should I do? How should I understand your comment? Is it fixed or not? Or is it a bug on https://code.videolan.org?