damienbod / angular-auth-oidc-client

npm package for OpenID Connect, OAuth Code Flow with PKCE, Refresh tokens, Implicit Flow
https://www.npmjs.com/package/angular-auth-oidc-client
MIT License
1.15k stars 435 forks source link

Error handling support for better user experience #976

Open GabrielGil opened 3 years ago

GabrielGil commented 3 years ago

Is your feature request related to a problem? Please describe.

I have successfully implemented this library in my project, however like in any other real-live app, errors do happen. Sometimes these errors are beyond the scope of our implementation, but still, they frustrate the experience of the users who do not know what to do.

Currently, when an error happens logging in the user is just redirected to the unauthorizedRoute with a generic "Unauthotized" message and error screen. There is no way to inform the user of what happened, since both this library, or the Authentication server may return an error.

I cannot find any mention to error handling in the docs.

Describe the solution you'd like Ideally, this library offers a mechanism to access the error returned, wither in the subscription observer of the checkAuth method, or as an event, or any other way the responsible team consider.

The most common error that happens in our app is "iat validation", but we cannot identify this error and inform the users that their time is not correct, and they have to fix this. We noticed more people than expected have their system time set ahead, in order to be always on time for their meetings and appointments.

However, other errors are found such a code which was issued with a different state etc.

Moreover, OIDC spec defines a set of errors that the server may return, which ideally can be accessed through the library (error, error_description, error_uri, state).

Ref: https://openid.net/specs/openid-connect-core-1_0.html#AuthError

Describe alternatives you've considered

I thought about these possible solutions:

Subscriber observer error.


enum AuthenticationErrorCode {
  // From Section 4.1.2.1 of OAuth 2.0 [RFC6749] https://tools.ietf.org/html/rfc6749#section-4.1.2.1
  /** The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. */
  invalid_request,
  /** The client is not authorized to request an authorization code using this method. */
  unauthorized_client,
  /** The resource owner or authorization server denied the request. */
  access_denied,
  /** The authorization server does not support obtaining an authorization code using this method. */
  unsupported_response_type,
  /** The requested scope is invalid, unknown, or malformed. */
  invalid_scope,
  /** 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.) */
  server_error,
  /** 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.) */
  temporarily_unavailable,

  // From OIDC https://openid.net/specs/openid-connect-core-1_0.html#AuthError
  /** The Authorization Server requires End-User interaction of some form to proceed. This error MAY be returned when the prompt parameter value in the Authentication Request is none, but the Authentication Request cannot be completed without displaying a user interface for End-User interaction. */
  interaction_required, 
  /** The Authorization Server requires End-User authentication. This error MAY be returned when the prompt parameter value in the Authentication Request is none, but the Authentication Request cannot be completed without displaying a user interface for End-User authentication. */
  login_required, 
  /** The End-User is REQUIRED to select a session at the Authorization Server. The End-User MAY be authenticated at the Authorization Server with different associated accounts, but the End-User did not select a session. This error MAY be returned when the prompt parameter value in the Authentication Request is none, but the Authentication Request cannot be completed without displaying a user interface to prompt for a session to use. */
  account_selection_required, 
  /** The Authorization Server requires End-User consent. This error MAY be returned when the prompt parameter value in the Authentication Request is none, but the Authentication Request cannot be completed without displaying a user interface for End-User consent. */
  consent_required,
  /** The request_uri in the Authorization Request returns an error or contains invalid data. */
  invalid_request_uri,
  /** The request parameter contains an invalid Request Object. */
  invalid_request_object,
  /** The OP does not support use of the request parameter defined in Section 6. */
  request_not_supported,
  /** The OP does not support use of the request_uri parameter defined in Section 6. */
  request_uri_not_supported,
  /** The OP does not support use of the registration parameter defined in Section 7.2.1. */
  registration_not_supported,

  // From Angular Oidc Connect
  /** Validation, iat rejected id_token was issued too far away from the current time */
  invalid_iat_time,
  /** State of the client and code do not match */
  invalid_state_validation,
}

/** 
 * An Authentication Error Response is an OAuth 2.0 Authorization Error Response message returned from the 
 * OP's Authorization Endpoint in response to the Authorization Request message sent by the RP.
 */
interface AuthenticationError {
  /** Error code */
  error: AuthenticationErrorCode;
  /** Human-readable ASCII encoded text description of the error. */
  error_description?: string;
  /** URI of a web page that includes additional information about the error. */
  error_uri?: string;
  /** REQUIRED if the Authorization Request included the state parameter. Set to the value received from the Client. */
  state?: string;
}

// In app.module 
checkAuth()
.subscribe(
  () => console.log('Success'), 
  (error) => console.log('Error happened', error)
);

This error could be sent to the "Unauthorized" route using OidcService.lastError$ for instance, where the last error would be available so the route can either handle it, or display it to the user in a UX-friendly way.

amiram commented 3 years ago

We really need this. In order not to break current API, I suggest that checkAuth will only throw an error if a specific param was supplied.

FabianGosebrink commented 3 years ago

In V12, we added an errorMessage to the checkAuth return value. This won't be a boolean alone anymore, but an object with more information.

I think throwing the error through an observable is a nice idea when logging in. The errors mentioned above can be thrown when logging in or when checking the auth (so coming back from the sts), is that correct?

Thanks

FabianGosebrink commented 3 years ago

Just checked: Can you provide a sample with a config from our samples and a reproducible error? What I do see is an error at the server, but we need it when coming back. If you provide a sample with an error, we can have a look.

GabrielGil commented 3 years ago

Yes @FabianGosebrink. The errors can come when logging-in or when checking the auth (hence, also when refreshing with the iframe).

The errors from the STS are included in the callback URL, so the library should check those and add them to the error object thrown. I have to check whether I can provide you with some reproducible errors. Let me get back to you at a later stage, please. :)

FabianGosebrink commented 3 years ago

We would have to map the errors in the url to a consistent error message. We already have a "new" error property in V12. Maybe this is doing it well, but I can test with your example then. Thanks!

michaelmarcuccio commented 2 years ago

In the case of IAT rejected as Gabriel mentioned, when using checkAuthIncludingServer() the errorMessage provided in the LoginResponse is undefined.

​ [WARN] 0-Client - authCallback Validation, iat rejected id_token was issued too far away from the current time ​ [DEBUG] 0-Client - authCallback token(s) invalid ​ [WARN] 0-Client - authorizedCallback, token(s) validation failed, resetting. Hash: ​ [ERROR] 0-Client - Error: Error: null ​ --at this point LoginResponse errorMessage = undefined.

I am looking forward to the full error handling feature to support all the different error scenarios so we can log to developers and notify users what is actually happening.

FabianGosebrink commented 1 year ago

Is this issue still relevant?

GabrielGil commented 1 year ago

I think it is. There's only an error property in the response, not really that it handles the different scenarios.

FabianGosebrink commented 1 year ago

We are handling the different scenarios or events with the PublicEventsService. This, however, is not only an error service. So we could use this Service and on error case, we send one of those events mentioned above. IN addition to that, we can have a lastError$ observable. Would that be okay or shouldn't we send error in the PublicEventsService, only "positive" events?

GabrielGil commented 1 year ago

The main problem I see with this is not actually the way errors are returned, but the errors themselves, as they are not typed at all. Most of the errors are part of the spec, and can be typed. Maybe a union, for those errors that cannot be mapped, so default to a generic object. The known errors should be objects, with errors codes along message descriptions. This is to allow the client app to identify the error by code, and not necessarily use the string (or show it in the console). Showing it to the client will not work for i18n apps.

mci-kmd commented 5 months ago

In my experience, the library just ignores the error message returned from the server. I have a failed login, redirecting to something like this:

http://localhost:4200/?error=invalid_request&error_description=MSIS9719%3a+The+code_challenge_method+is+not+supported.&state=...

Instead of containing the error message from the url. the checkAuth produces the error message Error: no code in url. While this is technically correct it is not particularly relevant since that will always be the case when there is an error like this.