Duplicate GET requests to OAuth flow redirect URI - facebook

I'm seeing a very strange issue with OAuth2 based social logins on my website. A small percentage of social login users are being redirected TWICE to the redirect URI used during the OAuth2 flow:
NOTE: This is not logging error, I'm seeing plenty of OAuth redirects which are hitting my servers just once.
This is also happening across a range of OAuth providers including Facebook and Google.
When I'm able to reproduce this in Chrome, I see the first request to the redirect_uri is cancelled in browser and the second request goes through successfully. This creates a small window for a race condition where the first request changes the session key on the server after successful login (to avoid session fixation vulnerabilities), so that when the second request hits the server with the old session key, it is missing state information in the session and the OAuth flow fails.
I have absolutely no clue what could be causing this as it's happening after the users are redirected to the 3rd party OAuth providers. Have you seen something similar on your production environments?

Related

Handling cookie not found error with oidc and keycloak

We have a Keycloak 18.0.2 with realms integrated to external IdP (Okta / Ping / Azure). In one realm we started seeing lots of IDENTITY_PROVIDER_LOGIN_ERROR / cookie_not_found in login events. And had users reporting that they were only able to login if they cleared their browser’s cache and cookies.
We found some ways to reproduce this behaviour in dev environment:
Multiple browser tabs:
At the browser, tab 1, user opens application menu-navigator and is redirected to the External SSO login page (after Keycloak auth flow redirection). We see this login page being initialized with cookie KC_RESTART (auth endpoint).
At the same browser, tab 2, user opens application dashboard-frontend and follows a similar flow as previous app.
Both websites are written in Vue.JS and are using oidc-client-ts library. When initialized, OIDC lib loads the realm metadata, creates a user storage, and executes signin flow. After user is authenticated and has an active session KC_RESTART cookie is expired.
As a result, when user back to tab 1 which is opened at step and try to login, user encounters an error since system cannot find authentication session and then try to use KC_RESTART cookie to reset authentication session however the cookie is even not there since it is expired at step c.
Then user is redirected to Keycloak and sees the Cookie not found message.
Stale sessions:
User opens the application (menu-nav or dashboard), sign in, do some operations, and close the tab.
Users opens the application again in a moment between Session Idle and Session Max timeouts.
User is redirected to Keycloak and sees the Cookie not found message.
We’ve seen the message in other situations, but we couldn’t consistently reproduce.
While we were debugging this problem, we saw a Keycloak redirection to applications call-back endpoint with erro=login_required. So, we decided to handle this and other possible errors trying to make the client app do a silent signin or do a signin redirect if signin silent fails.
This seems to be effective to that error and now we see some IDENTITY_PROVIDER_LOGIN_ERROR / expired_code which we believe it’s OK and associated with the session max timeout and makes user go to the SSO login screen.
Any thoughts about this?

Authorization callback URL from github not working, looping same page

So i'm building a website that requires the user to be github authenticated in order to create and publish blog posts via the netify cms.
so in github i've added the application.
the homepage is set as:
https://example.netlify.com
in order to reach the admin area of the site to post blogs and such the user must navigate to
https://example.netlify.com/admin/ and authenticate via github.
now the authentication part is working, i am logging into my github account but it loops back to the oauth page but with a weird url:
https://example.netlify.com/admin/?error=redirect_uri_mismatch&error_description=The+redirect_uri+MUST+match+the+registered+callback+URL+for+this+application.&error_uri=https%3A%2F%2Fdeveloper.github.com%2Fapps%2Fmanaging-oauth-apps%2Ftroubleshooting-authorization-request-errors%2F%23redirect-uri-mismatch&state=5d971eb88a5073cf804e90d5#/collections/blog
I've set my Authorization callback URL to:
https://example.netlify.com/admin/#/collections/blog
which is where the user should log in to, in order to post blogs and articles
so why does it keep looping? i've obviously set up a field incorrectly, i'm assuming that my Authorization callback URL to is incorrect?
Error = redirect_uri_mismatch
error_description=The+redirect_uri+MUST+match+the+registered+callback+URL+for+this+application.
error_uri=https%3A%2F%2Fdeveloper.github.com%2Fapps%2Fmanaging-oauth-apps%2Ftroubleshooting-authorization-request-errors%2F%23redirect-uri-mismatch
The main clue in this error message is redirect_uri_mismatch if you take that and google it you will begin to understand the problem. Part of the security with Identity servers is that the Redirct uri, that being the redirect uri that the Identity server returns your authorization to must be registered on the idnetiy server itself. Someone cant just send a request on behalf of your application and get the authorization back on their own site.
So what ever redirect uri you are sending in your application needs to be registered over on Github for the authorization to work. Its currently not.
You can read more about it here Troubleshooting OAuth App access token request errors

Facebook OAuth and redirect URI

Looking at Google OpenId connect and Facebook login I noticed that Facebook always hits the "redirect URI" from the browser. To explain a bit more - when user follows link of the form
https://www.facebook.com/dialog/oauth?client_id={app-id}&redirect_uri={redirect-uri}
S/he is always redirected to the URL specified by {redirect-uri}. This URL then contains "code" which can be exchanged for access token.
Google has similar approach but when using "server-flow" the {redirect-uri} is "hit" by Google's servers. This way the code which needs to be exchanged for access token never gets shown to the user.
It seems like FB's approach is less secure as the access code can actually end up in the attacker's hands.
Is there a way to have FB send the code to a redirect URI but not actually redirect client? Something like what Google does.
Does anyone have a good explanation why FB does not have "server-flow"-like behaviour but only "implicit-flow"?
You should grab the code from the server then use it, together with your app secret, to exchange an access token on Facebook. It does not matter if the client can see this because without the app secret, that code is useless. And the app secret is kept on your server, it is never revealed to the client.

Why is state variable necessary for Facebook login

Facebook's server-side login process mentions that the server should provide a 'state' variable during the request to Facebook. It acts like a CSRF token in which Facebook will pass back to login-callback page for our server to verify.
However I am not sure why this is necessary. If we do get a bogus login request, we still need to get the access_token from Facebook using the ?code= of the login request. A bogus request will not have the correct code, therefore won't work correctly.
Furthermore, a user can access our server through Facebook App's link. Facebook automatically adds a ?code= param to the link so our server can/have-to auto login the user. If we are to use code provided by this referral, we have no state param to verify anyways, and Facebook seems to not care about it either.
Is state optional? Does it really provide extra security?
The state parameter is optional. It may provide extra security if your service can put some information there worth keeping. Usually the state parameter is used for keeping data about the state of the session for the current browser window (as opposed to data dealing whole session which can be handled with cookies).
An example of using this feature for extra security would be doing following:
User visits your site
Your site sets session cookie (e.g. Set-Cookie: id=xyzrandomstuff)
User tries to do something that requires doing facebook authentication
Your site creates CSRF token that can be used with session id xyzrandonstuff and puts that in the state parameter for the facebook login
User completes login at facebook (you cannot see this)
User comes back from facebook with code and state parameters.
You verify login with code and before completing the action started at step 3 your server verifies that the CSRF token matches the current session.

Why can't I use server-side flow oauth tokens for an iOS app feed dialog?

I am working on a product which has both a desktop web site and a native iOS application. We are providing Facebook connect as a login option for our users in both contexts.
My intention was to share the same Facebook tokens via a secure JSON API for use in both contexts: when a user signs in on the web, the token is stored to our backend so that when the mobile client next runs, it can download the token and use it as well, and vice-versa. (* The detailed reasoning for this approach I explain at the end of the question, and is not essential to the question.)
The problem: when the iOS client uses a token to preset a feed dialog, if that token is generated by the web using the server-side flow, the dialog webview renders an error:
"An error occured with {my app name}. Please try again later."
This is reliably reproducible:
Generate a new access token using the server-side flow. Make sure you request publish_actions permission since you'll be using the feed dialog.
Using an incognito browser window (to get an empty cookie jar), view the m.facebook.com page that the iOS feed dialog would render in its webview: https://m.facebook.com/dialog/feed?access_token=SERVER_SIDE_FLOW_ACCESS_TOKEN&app_id=YOUR_APP_ID&redirect_uri=fbconnect%3A%2F%2Fsuccess&sdk=2&display=touch
Alternatively to #2 you could do all the work (which I have done already) of creating a dummy iOS app with the Facebook SDK, instantiating it correctly and presenting the dialog. It's just easier to go straight to the m.facebook.com feed URL for the purposes of reproducing the error.
If the token was generated by the auth flow initiated by the native Facebook iOS SDK instead of the server-side auth flow, the above feed url works perfectly fine, as expected.
Additionally, either token (mobile or server generated) works perfectly fine for posting feed items directly via the graph api. The problem is really just with the mobile feed dialog.
Is Facebook intentionally disallowing server-side generated tokens from operating in mobile feed dialog contexts?
Is this a bug with the feed dialog endpoint on m.facebook.com?
Or, hopefully, am I doing something wrong?
Why do I want to share tokens?
Since the offline_access permission is being removed, each client (web vs mobile) can benefit from having the other client refresh the same token when the user is active. This will lead to fewer instances of token expiry, and therefore fewer cases in which users must re-authenticate from scratch.
Likewise, users are not asked so frequently to approve additional permissions, since each client can benefit from the other's permission augmentations.
The tokens you get from the server side auth are different from the ones on the client side (I look at iOS/Android as client).
The server tokens are long lived one (60 days) while the client ones are short lived (a few hours).
The server side flow adds another layer of security where your servers authenticate against the facebook servers, which is probably why you get a long lived token automatically when using this flow.
If you try the debugger with an access token you will receive information about the token, such as the "origin" of the token.
For example a token generated from a client side auth (using js) has "Origin: Web".
That means that facebook indeed differentiate between tokens.
I'm not 100% sure about this, but from what you're saying it does sound like facebook is limiting the UI to the usage of client tokens and not server side ones, probably because the dialogs let the user do things without the need of the app to get permissions, and so if you have a 60 days token your app can then use it instead of the user and do things on his behalf with out having his permission.
I'm just guessing here.
What I would recommend you is to use the server token only on the server side, and let the iOS client handle his own token.
According to the Handling Invalid and Expired Access Tokens guide, it states:
iOS native applications
API errors are handled by the FBRequestDelegate interface. When you
detect an access token is invalid or has expired, your application
will need to multi-task over to the Facebook iOS app. Assuming the
user has not deauthorized your app, they will be immediately
multi-tasked back to your iOS application with a fresh, valid access
token.
Which means that you don't have to worry about the token getting expired on the client side.