Open yi-gu opened 1 year ago
Agreed. In addition, the RP might want to force an authentication, or the authentication level of assurance is not sufficient (both of which might trigger a re-authentication).
The FedCM API currently lacks a dedicated API surface that enables an IdP to return information about what went wrong and where to go from there to the RP or the browser. The only option is to overload the IdentityCredential.token parameter and rely on the API caller (IdP SDK, RP, or 3P library) to decode and display error messages. Unfortunately, this creates two problems:
A dedicated API surface would address these problems by providing a standard way for IdPs to return error information to RPs and/or users. This would allow IdPs to be confident that their errors would be handled correctly, and it would eliminate the need for RPs to special-case each IdP.
This is a proposal to introduce an API to inform the user agent about errors and handle them consistently across IdPs.
In this proposal, to guarantee to IdPs that errors are shown to inform users that their sign-in attempt has failed, the browser can support displaying error dialogs. To start with, a general purpose error dialog can cover a lot of cases and guarantee that users are notified in a consistent manner across RPs and IdPs. For example:
While the general purpose error dialog is better than the status quo, there are known error cases where a more purpose-specific dialog could give users more specific information. Therefore, it would be better if the browser can show some specific error dialogs to give the user more context. For example:
The enumeration of specific errors could be insufficient and it’s possible that users would want to learn more about the error and potentially some next steps to fix the error. Therefore the browser can provide some affordance to achieve that, e.g. via a new “More details” button:
When the user clicks the “More details” button, the browser can open a pop-up window to show a IdP controlled page with detailed information about the error.
In this proposal, to support the error dialogs described above, the browser introduces error codes and error urls that can be used whenever the IdP cannot produce a token.
In the id_assertion_endpoint
, currently the IdP can return a token
to the browser if it can be issued upon request. In this proposal, in case a token cannot be issued, the IdP can return an “error” response, which has two new fields:
OPTIONAL. The IdP can use the “code” field to specify one of the known errors from [invalid_request,unauthorized_client, access_denied, server_error and temporarily_unavailable]
. e.g.
// id_assertion_endpoint response
{
"error" : {
"code": "access_denied"
}
}
The list is based on the OAuth 2.0 error response table to cover common errors across IdPs.
If a valid code
is included in the response of the token request, the browser can trigger a native UI with proper strings to notify users that their sign-in attempt was failed due to the corresponding error. See considered alternatives for other options to show errors.
If an error
is provided but the code is not on the pre-defined list, we propose to pass the string to the API caller and show the uncustomized generic error UI above.
OPTIONAL. A URL identifying a human-readable web page with information about the error, used to provide additional information about the error to users. The uri must be of the same-origin as the IdP configURL
.
// id_assertion_endpoint response
{
"error": {
"url" : "https://idp.example/error?type=foo"
}
}
This field is useful to users because browsers cannot provide rich error messages on a native UI. e.g. links for next steps, customer service contact information etc.. If a user wants to learn more about the error details and how to fix it, they could visit the provided page from the browser UI for more details.
[!NOTE]
- Both
code
andurl
are optional in case of token request failures. The browser should provide a fallback UI to keep users aware if both are missing.
- The new browser error UI may conflict with existing error UI rendered by the RP if any. That said, the risk is extremely low based on how we see current IdPs implementing FedCM.
- We believe that the browser should be opinionated to render a fallback error UI to make sure that users are informed when their sign-in attempt has failed.
- It’s possible that there’s no response returned from the
id_assertion_endpoint
in which case the browser cannot be sure that an error occurred. Without a time-out mechanism, the browser won’t show any UI in this case.
To give more context to the API caller such that they could provide more sign-in options to users, the browser can pass over the error (code
and url
) by failing the promise:
try {
await avigator.credentials.get({
identity: {
// ...
}
});
} catch (e) {
// Acquire {code, url} from an `IdentityException`
}
The new Error Response API is only invoked post user permission to allow RP/IdP communication. e.g. the user is aware that they are “signing in to RP with IdP”. In addition, the IdP has already possessed both the RP information and the user cookie from the id_assertion_endpoint
. Therefore we believe that there’s no change in the privacy threat model with the new API.
When the user clicks the “More details” button, we open a popup (same UI and web platform properties as what one would get with window.open(url,””,”popup,noopener,noreferrer”)) that loads the error.url
. Note that no communication between the website and this pop-up is allowed (e.g. no postMessage, no window.opener).
The primary threat is a phishing attack, where the attacker (who controls - or colludes with - both the RP as well as the IdP) can provide a fake “error.url” (that impersonates a real IdP) for the browser to display via the pop-up window, and trick the user to enter their (real IdP) password there. e.g. upon user clicking the “More details” button, the browser will open a page that looks like a genuine “Sign in to IdP” website. Then the user “may” be tricked into entering their IdP credentials on that website.
Because of that, the pop-up window has the following properties:
configURL
) that is being loadedAs such, the attack has to rely on the fact that the user misses the displayed origin/site in both steps and on safe browsing not knowing about the site.
In addition to that, the attacker may already be able to do this by opening a phishing pop-up window and there’s no browser UI involved compared to this proposal.
The browser could delegate handling the errors to the API callers (RP or IdP SDK or FedCM library owned neither by RP nor IdP).
When the browser receives the errors from the token request, it rejects the promise with the errors. Once the API caller receives the error, the caller can inform users accordingly. e.g. the caller can render an iframe to show proper information to users.
Pros
Cons
OAuth 2.0 Error Response | Error | Description | Included in this proposal | |
---|---|---|---|---|
invalid_request | The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. | Yes | ||
unauthorized_client | The client is not authorized to request an authorization code using this method. | Yes | ||
access_denied | The resource owner or authorization server denied the request. | Yes | ||
server_error | The authorization server encountered an unexpected condition that prevented it from fulfilling the request. (This error code is needed because a 500 Internal Server Error HTTP status code cannot be returned to the client via an HTTP redirect.) | Yes | ||
temporarily_unavailable | The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server. (This error code is needed because a 503 Service Unavailable HTTP status code cannot be returned to the client via an HTTP redirect.) | Yes | ||
unsupported_response_type | The authorization server does not support obtaining an authorization code using this method. | No. FedCM is authentication focused at the moment and we can add this type in the future. | ||
invalid_scope | The requested scope is invalid, unknown, or malformed. | No. FedCM is authentication focused at the moment and we can add this type in the future. |
Since the error is anyway exposed to the RP ("The Client JS API"), wouldn't it make more sense to let the RP take care of showing error messages (which it could do without a popup), and let this just be a proposal about exposing the error type to the RP?
Having the API caller rendering the UI does have benefits as mentioned in the considered alternatives section. That said, there's no guarantee that the API callers would do it and the UX would be bad if they don't. In addition, when it comes to multi-IdPs, it would be challenging for them to implement a proper UI affordance to show error messages.
As noted in the call:
Thanks Achim for following up!
This can be achieved using continue_on and then being able to do something like IdentityProvider.reject('{error:.....}')
This is indeed a possible solution and we started with it when developing this feature. It's worth noting that this may lead to suboptimal UX. In Chrome, the error page will be displayed in a pop-up window on desktop and in a CCT (ChromeCustomTab) on Android. For UI that's as loud as a pop-up or CCT, it should provide as much value as possible. In the AuthZ API (name TBD), user can grant calendar access to finish the sign-in flow; in IdPLoginStatus API, user can sign in to IdP via the pop-up. However, for the error page, it's unlikely to be call-to-action. Rather, it's likely to just provide some information about the error itself. Therefore we believe that it should be gated by a quieter UI and only be displayed if user wants to "learn more". Ideally the browser UI can provide meaningful strings such that the user doesn't need to open the loud UI.
why not simply let the IDP provide error and error_description and render it?
Unfortunately IdP doesn't have a reliable way to do it. If FedCM API is called by RP directly (or via 3P library) without using IdP SDK, then the error may not be (properly) handled. This could be concerning to IdPs. Even if an IdP SDK is used, when it comes to multiple IdP, it's possible that different IdPs handle errors differently which could cause inconsistent UX on the same RP.
... Ideally the browser UI can provide meaningful strings such that the user doesn't need to open the loud UI.
I'm good with having both options - both paths should be generally possible though.
Unfortunately IdP doesn't have a reliable way to do it. If FedCM API is called by RP directly (or via 3P library) without using IdP SDK, then the error may not be (properly) handled. This could be concerning to IdPs. Even if an IdP SDK is used, when it comes to multiple IdP, it's possible that different IdPs handle errors differently which could cause inconsistent UX on the same RP.
That's not what I meant - I was referring to this:
If a valid code is included in the response of the token request, the browser can trigger a native UI with proper strings to notify users that their sign-in attempt was failed due to the corresponding error. See considered alternatives for other options to show errors.
If an error is provided but the code is not on the pre-defined list, we propose to pass the string to the API caller and show the uncustomized generic error UI above.
Given this error UI is shown post consent why not simply define
// id_assertion_endpoint response
{
"error": {
"error_code": "OIDC or SAML standard". --- not used in UI, but for SDKs and RPs to have aligned error handling between FedCM based on Redirect flows.
"error_short" : "Short description", --- Headline for UI above - if not given default error text
"error_description": "Long Descritton". --- Detailed Text for UI - if not given default error text
"url": "https://"
}
}
IDP can use the strings they'd want from any standard they want to use, if this a IDP controlled UX
This should also be passed back to the RP in case they'd want to react to this.
Hi Achim, allowing customized error description occurred to us as well. We should have mentioned it in the "Considered Alternatives" section. Sorry about that!
The question is: whether / how much should the browser allow random strings on its native UI. The conclusion we have at least in Chrome is that it should be "little to none" from security's perspective. We shared the same principle when designing the RP context API where we only allow 4 predefined strings (sign in, sign out, user, continue) instead of accepting customized ones.
From extensibility's perspective, we allow random error string in code
so if an IdP prefers to show customized error UI with their SDK (or with proper developer documentation such that RP can handle the error properly by themselves), they could send "code":"foo"
back to the browser and and the browser will hand it over to the API caller. The API caller can then parse the error and handle it accordingly. The only difference is that a native browser UI will show up with a generic error string (see mock above). We believe that A browser UI in this case is necessary because there's no guarantee that the API caller will handle "code":"foo"
(properly) therefore the browser should close the authentication flow by itself.
WDYT?
We shared the same principle when designing the RP context API where we only allow 4 predefined strings (sign in, sign out, user, continue) instead of accepting customized ones.
From a thread model perspective this seems a different case given this is shown in the account selector during the main mediation. Whereas the error is an alternative to passing a token to the RP post user interaction.
From extensibility's perspective, we allow random error string in code so if an IdP prefers to show customized error UI with their SDK (or with proper developer documentation such that RP can handle the error properly by themselves), they could send "code":"foo" back to the browser and and the browser will hand it over to the API caller. The API caller can then parse the error and handle it accordingly. The only difference is that a native browser UI will show up with a generic error string (see mock above). We believe that A browser UI in this case is necessary because there's no guarantee that the API caller will handle "code":"foo" (properly) therefore the browser should close the authentication flow by itself.
If the generic error UI is mandatory than no IDP or RP would mind showing a second error honestly, the error JSON passed to the RP is then likely only used for failure analysis. Its an additional complexity for browsers and IDP (supported error codes + no ability for a bespoke description (which is left open to the IDP in OAuth/OpenID) - not a deal breaker but makes continue_on even more pressing as a different option.
@asr-enid can you clarify what is your preferred behavior? In particular, how should errors be handled if the IDP id assertion endpoint has some network error?
If the generic error UI is mandatory than no IDP or RP would mind showing a second error honestly
Alternatively the IdP can provide an error URL without a predefined code. The browser can then construct the UI with generic error string + a "More details" button. If the user is interested, they can definitely learn about the detailed error from the pop-up window. If they are not interested, we don't need to show the loud UI which also respects the user's choice (not interested).
@asr-enid can you clarify what is your preferred behavior? In particular, how should errors be handled if the IDP id assertion endpoint has some network error?
In this case a generic error UX seems sensible as the browser won't have any information available (an error JSON) and the browser mediated flow just fails technically. Would something be passed back to the caller in this case?
Overall the approach suggested is doable I guess, but as noted managing the supported codes to avoid having the IDP provide the content directly can become a burden. I made the case for continue_on already. If these things are optional / available for the IDP they can decide which path to follow given the scenario at hand
continue_on would additionally adress cases where the login needs extra work or re-authentication as noted by @philsmart as the error can be avoided if the user is able to take the necessary action.
continue_on would additionally adress cases where the login needs extra work or re-authentication as noted by @philsmart as the error can be avoided if the user is able to take the necessary action.
continue_on
is definitely useful (e.g. handle parental control as mentioned in this issue) and it's not mutually exclusive with this proposal :)
continue_on is definitely useful (e.g. handle parental control as mentioned in this issue) and it's not mutually exclusive with this proposal :)
Agreed
@samuelgoto seems this should be a proposal under https://github.com/fedidcg/FedCM/tree/main/proposals ?
Currently after a user has granted permission to sign in to an RP with IdP (e.g. by clicking on the "Continue as" button on the account UI), the browser will send a request to the id_assertion_endpoint to fetch an IdentityProviderToken. If a token is issued, the browser will hand it over to the API caller and the user will continue their journey on the website.
However, the IdP could refuse to issue an token. To name a few:
Today when the IdP refuses to issue an token, FedCM API will reject the promise and fail the request silently.
From user's perspective, after they have granted permission to sign in, they may be confused because they are neither signed in nor informed about the failure. It would be helpful if there's a way for keep users in the loop for better user experience.