Custom jwt validation in Strapi v4. ValidationError

System Information
  • 4.10.5:
  • Windows:
  • sqlite:
  • Node version 16.20:

I needed to use Firebase authorization through a phone number. I developed code based on this topic: Is custom JWT Validation available in V4? and put it in the index.js file. This is a custom strategy that has 2 functions - authenticate and verify. They work correctly as evidenced by the console.log output in the verify function.

eyJhbGciOiJIUzI1N...cHyVcggxV0yGhQf6tNbN8
Token is valid { id: 1, iat: 1686254318, exp: 1688846318 }

But I don’t understand at all why the error appears in the console:
ValidationError: 2 errors occurred

I’m expecting a ctx to be returned to me on the frontend application. Please help me understand the problem and fix it. I have been struggling with this for a week now.

index.js:

"use strict";
const { ForbiddenError } = require('@strapi/utils').errors;

module.exports = {
  /**
   * An asynchronous register function that runs before
   * your application is initialized.
   *
   * This gives you an opportunity to extend code.
   */
  register({ strapi }) {
    strapi.container.get('auth').register('content-api', {
      name: 'firebase-jwt-verifier',
      async authenticate(ctx) {
        const { authorization } = ctx.request.header;
      
        // Check for JWT token in the header
        if (authorization) {
          const parts = authorization.split(/\s+/);
          if (parts[0].toLowerCase() === 'bearer' && parts.length === 2) {
            const token = parts[1];
      
            try {
              // Verify and decode Firebase JWT token
              const decodedToken = await strapi.firebase.auth().verifyIdToken(token);
      
              // Check if the phone number in the JWT token matches the incoming request
              if (decodedToken.phone_number === ctx.request.body.phoneNumber) {
                // Get user or create a new user based on the phone number
                let user = await strapi.db.query('plugin::users-permissions.user').findOne({
                  where: { phoneNumber: ctx.request.body.phoneNumber }
                });
      
                if (!user) {
                  // If the user doesn't exist, create a new user
                  user = await strapi.db.query('plugin::users-permissions.user').create({ phoneNumber: ctx.request.body.phoneNumber });
                }
      
                // Set the user in the context state
                ctx.state.user = user;

                // Generate Strapi JWT token
                const jwtToken = await strapi.service('plugin::users-permissions.jwt').issue({ id: user.id });
                ctx.state.user.jwt = jwtToken
      
                // Return successful authentication and user information
                return { authenticated: true, credentials: user };
              }
            } catch (error) {
              // Handle error during token verification or decoding
              console.error('Error verifying Firebase JWT token:', error);
            }
          }
        }
      
        // If authentication fails, return authentication error
        return { authenticated: false };
      },      

      async verify(ctx) {
        console.log(ctx)
        try {
          // Check for jwt token in ctx.state.auth
          if (ctx.credentials && ctx.credentials.jwt) {
            const token = ctx.credentials.jwt
            console.log(token)
            const tokenPayload = await strapi.service('plugin::users-permissions.jwt').verify(token);
            console.log('Token is valid', tokenPayload)
            return
          }
        } catch (error) {
          console.error('Error verifying JWT token:', error);
        }
      }
      
    });
  },

  /**
   * An asynchronous bootstrap function that runs before
   * your application gets started.
   *
   * This gives you an opportunity to set up your data model,
   * run jobs, or perform some special logic.
   */
  bootstrap({ strapi }) {
    const admin = require("firebase-admin");
    const serviceAccount = require("../private/firebase/serviceAccountKey.json");
    admin.initializeApp({
      credential: admin.credential.cert(serviceAccount),
    });

    strapi.firebase = admin;
  },
};
1 Like

Here is the full validation error log:

ValidationError: 2 errors occurred
    at handleYupError (C:\Users\Super\Desktop\strapi\node_modules\@strapi\plugin-users-permissions\node_modules\@strapi\utils\lib\validators.js:67:9)
    at C:\Users\Super\Desktop\strapi\node_modules\@strapi\plugin-users-permissions\node_modules\@strapi\utils\lib\validators.js:79:7
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async Object.callback (C:\Users\Super\Desktop\strapi\node_modules\@strapi\plugin-users-permissions\server\controllers\auth.js:49:7)
    at async returnBodyMiddleware (C:\Users\Super\Desktop\strapi\node_modules\@strapi\strapi\lib\services\server\compose-endpoint.js:52:18)
    at async policiesMiddleware (C:\Users\Super\Desktop\strapi\node_modules\@strapi\strapi\lib\services\server\policy.js:24:5)
    at async C:\Users\Super\Desktop\strapi\node_modules\@strapi\strapi\lib\middlewares\body.js:58:9
    at async C:\Users\Super\Desktop\strapi\node_modules\@strapi\strapi\lib\middlewares\logger.js:9:5
    at async C:\Users\Super\Desktop\strapi\node_modules\@strapi\strapi\lib\middlewares\powered-by.js:16:5
    at async cors (C:\Users\Super\Desktop\strapi\node_modules\@koa\cors\index.js:107:16)

As the yup error log is quite unspecific about the line that raises this error, I would guess it is this one:

user = await strapi.db.query('plugin::users-permissions.user').create({ phoneNumber: ctx.request.body.phoneNumber });

Please try to wrap your new user object within “data”:

user = await strapi.db.query('plugin::users-permissions.user').create({ data: { phoneNumber: ctx.request.body.phoneNumber }});

Did this solve your issue?

I found the best solution for authorization through Firebase and it works. I will definitely write a big article about this as soon as I find free time.

Could you send a solution please. Struggling with this almost 3 days already.

1 Like