remix-auth icon indicating copy to clipboard operation
remix-auth copied to clipboard

Pass custom error data

Open danieltott opened this issue 2 years ago • 2 comments

This is more of an idea than a finalized PR - I'm not sure if this approach is the best one, and supporting it fully would require changes to the other community Strategies.

I have been trying to get custom data passed through when throwing an error inside an authentication call (same as this discussion).

Usually in these situations, I create a custom error class where I can add data to an error message:

export class FancyError extends Error {
  constructor(message, data) {
    super(message);
    this.data = data;
  }
}

throw new FancyError('Uh oh', {moreData:'yes'})

try{
  throw
} catch(error) {
console.log(error.message) // Uh oh
console.log(error.data) // {moreData:'yes'}
}

The problem is that remix-auth and at least remix-auth-form pass errors around by calling new AuthorizationError(error.message) instead of passing through the original error object. This strips away any custom functionality.

This PR adds a simple errors[] list to the AuthorizationError class, and allows passing an errors list into authenticate.failure.

To actually make use of this, you'd have to throw new AuthorizationError(message,errors) inside your authentication call, and update your strategy to pass error.errors as the final parameter of failure.

Here's remix-auth-form with that enabled:

export class FormStrategy extends Strategy {
  name = 'form';

  async authenticate(request, sessionStorage, options) {
    let form = await request.formData();

    let user;
    try {
      user = await this.verify({ form, context: options.context });
    } catch (error) {

      let message = error.message;
      return await this.failure(
        message,
        request,
        sessionStorage,
        options,
        error.errors,
      );
    }

    return this.success(user, request, sessionStorage, options);
  }
}

And how I'd use it in the app:

authenticator.use(
  new FormStrategy(async ({ form }) => {
    const api = new Api();

    let email = form.get('email');
    let password = form.get('password');

    try {
      return await api.login(email, password);
    } catch (error) {
      // this assumes my api is sending in error.data.errors - you could set any list of strings as the second argument
      throw new AuthorizationError(error.message, error.data?.errors);
    }
  }),
  'user-pass'
);

danieltott avatar May 26 '22 20:05 danieltott

Forgot to mention, but the errors object is completely optional in failure and AuthorizationError - so there'd be no breaking changes.

danieltott avatar May 26 '22 20:05 danieltott

If this was accepted, I'd be happy to file PRs with the strategies listed in the discussion 👍🏼

danieltott avatar May 26 '22 20:05 danieltott

This was added in v3.4.0

sergiodxa avatar Feb 23 '23 06:02 sergiodxa