msuliq / omniauth_oidc

Omniauth strategy to authenticate and retrieve user data using OpenID Connect (OIDC)
MIT License
0 stars 0 forks source link

OpenIDConnect::Forbidden error on callback (insufficient scope) #2

Open Augustin-Grenne opened 4 days ago

Augustin-Grenne commented 4 days ago

Dear developer thank you for this gem repo. I've been trying to implement an authentication flow with OpenID Connect in our RoR application, but so far with no luck. I'm sorry if this is not the right place to ask the question, as I'm unsure if the issue is related to this gem (but it might). I've set up the middleware config, and paths as described and a user is correctly redirected starting an OAuth flow. However the callback does not seem to hit the specified controller action but instead I see an error page.

Screenshot 2024-07-02 at 11 10 20

The provided scope is [:openid, :rrn], changing the scope to only :openid or expanding it with other (unnecessary) options like :profile result in the same error.

I'm unsure what causes this error, but I would be very grateful if you'd be able to shed some light on the issue. Thanks in advance.

msuliq commented 4 days ago

Hi @Augustin-Grenne, From the looks of it the OIDC provider is responding with 403 Forbidden when the Rails app + gem are attempting to exchange access token for user info. The most likely cause is that the access token does not support the requested scope (requested by the Rails app when making a call to OIDC provider’s user info endpoint).

When you visit the config_endpoint_url, what scopes are listed there?

By default if there is no manual configuration in the initializer for the scope, the gem applies for all scopes listed by the config_endpoint_url, but we can override manually specifying the needed scopes in the initializer. If the access token is not authorized to request a scope, can you please modify omniauth initializer to include following?

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :oidc, {
    # Your configuration for name, endpoints, etc.
    scope: [:openid], # let's try with only openid and nothing else, no :email or :group, etc.
    }
  }

Scope for :openid is usually always supported and see if this resolves your issue. If yes, you can try adding more scopes that are shown in the config_endpoint_url one by one until you find the one that is throwing 403 Forbidden. Once found, then just don't request it and list all other scopes in the initializer. If the problematic scope is absolutely necessary for your application, you will have to contact OIDC provider for support on how to properly to request that scope.

If that does not help, would you mind sharing config_endpoint_url or name of your OIDC provider and I can try to reproduce the issue on my side

Augustin-Grenne commented 4 days ago

@msuliq Thanks for your response. The discovery/config endpoint url https://authenticatie-ti.vlaanderen.be/op/.well-known/openid-configuration lists two accepted scopes: openid and webid, adding only openid, as well as adding both scopes ([:openid, :webid]) to the omniauth initializer results in the aforementioned scope error. Additionally leaving out the issuer, as in your 'simple provider' example results in an

undefined local variable or method `issuer' for #<OmniAuth::Strategies::OIDC>
msuliq commented 3 days ago

@Augustin-Grenne I have tested out some of the hypothetical places in code that might not be handling this case but so far the gem code seems to work fine. Referring to generic OIDC documentation, 403 is thrown in case the access token that we obtained for a user does not have permissions from the OIDC provider to request JWT with user info. I looked into documentation for Flemish Access Management using in-browser translator and there is a paragraph saying that integration application has to explicitly mention that client will use the userinfo endpoint

Aside from that could you please share full trace of the response from the OIDC server, either from console or "Network" tab in your browser's developer tools? Thank you

Augustin-Grenne commented 3 days ago

@msuliq Thank you very much for all your help. I've checked our integration application and it seems we didn't check that box for some reason. I'll be in touch with our contact about this. However there's actually only one piece of information that our application needs and that is the rrn (rijksregisternummer) which is a separate scope, also in the integration application, which we did check. However this scope is not listed as 'acceptable' on the config endpoint. I'll ask my contact about that too. Do you know of a way to skip sending a request to the user info endpoint? That way I can verify the issue is indeed related to this as we expect. Which would be insightful while we wait on more info from my contact. Thanks again for your help.

msuliq commented 3 days ago

Unfortunately, there is no config option to skip call to the userinfo_endpoint, since the whole point of this gem is to fetch user's data from that endpoint. One way to make sure that this call is the root cause would be to clone this repo, install the gem in your Rails app from your local directory: gem 'omniauth_oidc', path: '/path_to_the_directory_with_cloned_repo' First you will need to modify Gemfile to add the debugger of your preference, run bundle install and place a breakpoint right at the beginning of the user_info_from_access_tokenmethod incallback.rbfile before it makes http call to theuserinfo_endpoint. From there you should be able to see parameters of the request to theuserinfo_endpoint` as well as server response

Augustin-Grenne commented 3 days ago

I've shot a lengthy email to my contact with our findings and to ask to make some changes in our integration. I hope to hear soon from them. But it might be until Tuesday, earliest, that I can resume with this. I've cloned the repo in development and put the debugger where you said, but hit a JSON parse error before hitting the breakpoint. I didn't dig in much deeper as it doesn't seem the most optimal path to resolve my core issue.

Screenshot 2024-07-03 at 11 43 07

Since you asked here is a full Network trace of the call (or were you more interested in the case when no issuer is provided?)

# Request
:authority: authenticatie-ti.vlaanderen.be
:method: GET
:path: /op/v1/auth?client_id=9547c460-8bef-435d-b16d-fea18a4823cc&nonce=8&redirect_uri=https%3A%2F%2Freview.bpart-staging.eu%2Fburgerbegroting%2Fvoters%2Fauth%2Foidc%2Fcallback&response_mode=query&response_type=code&scope=openid%20rrn&state=7
:scheme: https
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8,nl;q=0.7
Cache-Control: max-age=0
Cookie: tbsession=YibQjIeWLb__46Z5WgQ.iBARrPHFaaentLBauqQbGE7rWUFju0kDl.DDvC6aMz0t; vo-authmethod=itsme; vo-cot=vo; PD_STATEFUL_1e86fa40-98d3-11e9-85af-f73b02aa33ed=%2Fsps; IV_JCT=%2Fsps; vo-taalkeuze=NL; AMWEBJCT!%2Fsps!JSESSIONID=2b5ed8e2776f8066~0000QYYnUrh4ewp2G8ZY5ZfvsU1:e304ab04-69c8-49a6-87e5-16310b9c8cfd:ed26dbec-5858-45c0-a942-6216cf3009c3:fa38f714-d063-41c6-b69a-2908653fd6c2; VOGTIANONUSER=rB/ge2aFf590swAWBrR8Ag==; AMWEBJCT!%2Fsps!https%3A%2F%2Fauthenticatie-ti.vlaanderen.be%2Fsps%2Fspfas2%2Fsaml20FIMSAML20=uuid933e50a1-4b24-4e6e-8483-d581c637ffa5; PD-H-SESSION-ID=1_+9XjySewIDmpYYix7uh45mXnuoewrZJPPdiH5RAkY0JpVqcb7BY=_AAAAAQA=_oxM8O+IYTf+cvXt9t9UZGi/mDuA=
Dnt: 1
Priority: u=0, i
Sec-Ch-Ua: "Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Gpc: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36

# Response
Content-Type: text/html; charset=utf-8
Date: Wed, 03 Jul 2024 16:43:39 GMT
Location: https://review.bpart-staging.eu/burgerbegroting/voters/auth/oidc/callback?code=qUE02fxUP-c&state=7
P3p: CP="NON CUR OTPi OUR NOR UNI"
Referrer-Policy: no-referrer
Server: nginx
Strict-Transport-Security: max-age=31536000; includeSubDomains
Strict-Transport-Security: max-age=15768000
X-Content-Type-Options: nosniff
X-Correlation-Id: bcd90022-680d-487e-85ac-3fa1f7819519
X-Group-Correlation-Id: rB/ge2aFf590swAWBrR8Ag==
X-Old-Content-Length: 125
X-Vo-Op-Request-Id: ebd249dd-0c8b-4467-9738-a76f6a22bfa8
X-Xss-Protection: 1; mode=block
msuliq commented 11 hours ago

@Augustin-Grenne I published version 0.2.0 of this gem which has an optional parameter that will allow your app to skip the part of fetching data from the user_info_endpoint and it will return only access token-related parameters to the controller in your app.

There is also a new section in readme dedicated to this use case