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

Getting 401 Unauthorized on callback #65

Open
mikeover opened this issue Nov 4, 2016 · 14 comments
Open

Getting 401 Unauthorized on callback #65

mikeover opened this issue Nov 4, 2016 · 14 comments

Comments

@mikeover
Copy link

mikeover commented Nov 4, 2016

I'm getting a 401 Unauthorized during the callback phase and it's not clear why. I'm wondering if I could have something wrong with my configuration or if there's an issue on the provider's side. The line it's failing on is this:

 elsif request.params['state'].to_s.empty? || request.params['state'] != stored_state
     return Rack::Response.new(['401 Unauthorized'], 401).finish

I've logged the variables in the if statement and see that state is present but stored_state is empty. Why might this be?

FWIW, here's my configuration:

           :name                    => "my name",
           :scope                   => [:openid, :email],
           :response_type           => :code,
           :client_signing_alg      => :RS512,
           :client_x509_signing_key => <my_certificate_here>,
           :discovery               => true,
           :issuer                  => <my_discovery_issuer>,
           :client_options          =>
           {
             :port         => 443,
             :scheme       => "https",
             :host         => <my_host>,
             :identifier   => <my_client_id>,
             :redirect_uri => <my_redirect_uri>
           }

Is there something I could be missing in my configuration?

Note that this is an integration with a 3rd party, not an established OpenID provider like Google or anything.

Thanks.

@cmrd-senya
Copy link

Be careful with logging stored_state method result, since it is a "single-read" function, once you access it, it'll return nil on every successive call. See implementation:

      def stored_state
        session.delete('omniauth.state')
      end

What does your authorize URI and callback URI look like?

As a debug measure you may try to replace stored_state impelemntation with something like

      def stored_state
        session['omniauth.state']
      end

and check what value it will show this way.

@mikeover
Copy link
Author

mikeover commented Nov 4, 2016

I only started logging it once I got that 401. But if I just print out the whole session instead (before that delete is called), the session doesn't even contain omniauth.state, just a session_id.

And actually if I log the session when new_state is called, the session looks fine, but when I get the response, the session has a different session_id and nothing else.

@cmrd-senya
Copy link

What does your authorization request URI and callback URI look like? You may stub domain name when posting these if it's an issue for you.

@mikeover
Copy link
Author

mikeover commented Nov 4, 2016

/auth/:provider/callback(.:format) => sessions#create

And the /auth/:provider which is built into the gem I believe

@cmrd-senya
Copy link

cmrd-senya commented Nov 4, 2016

No, by authorization URI I mean the URI you get redirected, when you access your /auth/:provider route.

For instance, given my OIDC client is client.example.com and authentication server is server.example.com, then when I access https://client.example.com/auth/diaspora I'm redirected to https://server.example.com/authorizations/new?client_id=d6c1bf14f5310c4ff9c0c904ac93a6a5&nonce=8198116227d74e66248a14fe0c8e6543&redirect_uri=https%3A%2F%2Fclient.example.com%auth%2Fprovider%2Fcallback&response_type=code&scope=openid+name+profile&state=d311c69977de6bda0ee7b63eb3d9164c. The latter is "authorization URI".

@mikeover
Copy link
Author

mikeover commented Nov 4, 2016

Here is the config from the discovery:

{"issuer":"https://domain.com/PortalSSO","jwks_uri":"https://domain.com/PortalSSO/.well-known/jwks","authorization_endpoint":"https://domain.com/PortalSSO/connect/authorize","token_endpoint":"https://domain.com/PortalSSO/connect/token","userinfo_endpoint":"https://domain.com/PortalSSO/connect/userinfo","end_session_endpoint":"https://domain.com/PortalSSO/connect/endsession","check_session_iframe":"https://domain.com/PortalSSO/connect/checksession","revocation_endpoint":"https://domain.com/PortalSSO/connect/revocation","introspection_endpoint":"https://domain.com/PortalSSO/connect/introspect","frontchannel_logout_supported":true,"frontchannel_logout_session_supported":true,"scopes_supported":["openid","eportal","email","address","g_strUserSecurityGroups","mod_dates"],"claims_supported":["sub","userName","userEmail","userMod_date","userProfile","userGroups","userPhones","userAddresses","userBasicInfo","employerName","employerprofile","employerDealerCode","employerDivision","email","email_verified","address","g_strUserSecurityGroup","mod_date"],"response_types_supported":["code","token","id_token","id_token token","code id_token","code token","code id_token token"],"response_modes_supported":["form_post","query","fragment"],"grant_types_supported":["authorization_code","client_credentials","password","refresh_token","implicit"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["RS256"],"code_challenge_methods_supported":["plain","S256"],"token_endpoint_auth_methods_supported":["client_secret_post","client_secret_basic"]}

PS: I'm using discovery

@cmrd-senya
Copy link

It is not that significant what you've got in discovery, since it seems to pass well. When your client is in the request phase it must generate state value and then construct a URI, where web browser is getting redirected afterwards. state must be in the URI's parameter list.

@mikeover
Copy link
Author

mikeover commented Nov 4, 2016

Is it not significant that I seem to have a different session on the callback than I did when making the request?

@cmrd-senya
Copy link

cmrd-senya commented Nov 4, 2016

The session must be the same, of course. But that got nothing to do with the discovery. The request is initiated after the discovery has been finished. state paremeter is first generated after the discovery as well. Discovery is an optional part and doesn't necessarily happens in the general case.

@mikeover
Copy link
Author

mikeover commented Nov 4, 2016

I'm also going through an ngrok tunnel, or rather the provider is hitting
ngrok and getting forwarded to my local box. I suppose this could be the
reason for the session resetting?

On Nov 4, 2016 3:11 PM, "Senya" notifications@github.com wrote:

The session must be the same, of course. But that got nothing to do with
discovery. The request is initiated after the discovery has been finished.
state paremeter is first generated after the discovery as well. Discovery
is an optional part and doesn't necessarily happens in the general case.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#65 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AEz9igsAMh4KUxlniWtZBsudgvamDDdWks5q64NdgaJpZM4Kpyjn
.

@cmrd-senya
Copy link

Do you initiate your authorization by hitting /auth/:provider path of your box through your ngrok tunnel?

@mikeover
Copy link
Author

mikeover commented Nov 4, 2016

It's the same result when I do, it ends up with a new session on callback

@cmrd-senya
Copy link

I think there must be something wrong either with your web application configuration or with your environment. Maybe some CSRF issue, for example (you must turn off CSRF verification for omniauth routes in Rails).

I'd still compare authorization URI with callback URI to see whether the state parameter presents there. Just to make sure it is generated properly in request and passed properly to callback by the authorization server.

@mikeover
Copy link
Author

mikeover commented Nov 7, 2016

In the authorization URI, the state variable is set to true:

https://domain/PortalSSO/connect/authorize?client_id=my_client&nonce=0c456f392c5a9608fee2dff6e5b10520&redirect_uri=http%3A%2F%2Fmy_domain%2Fauth%2Fprovider%2Fcallback&response_type=code&scope=openid+eportal+email&state=true

Callback request parameters are:

{"code"=>"e986c68785685f32545c67220602a689", "state"=>"true", "session_state"=>"E_635pAf9ohmdDJ0uinGja9kfuWbxB8PUWqGhYeFINg.3757da22727ab432048a783a39f2e72b"}

However, at this point my session has just a session_id, nothing else, so comparing request["state"] against session["omniauth.state"] fails.

I imagine the issue has to do with the session resetting between auth and callback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants