Handling external auth callback within a custom plugin

I’m writing a custom plugin where logged-in users can:

  • login to to their GitHub account
  • call a route to export some data as JSON
  • push it to our project GitHub repository if they have access

I set up a public route for GitHub to send the authorization code once logged in, for example: /plugin/callback

The route needs to be public otherwise GitHub wouldn’t be able to send the token. With a controller I can store the token in the plugin’s content type, but without any user information because that’s not available in a public route.

This means that even though the authorization code is stored, the client can’t know which user it belongs to!

How can I deal with this?

  • Can I access any user data from a controller attached to a public route? Just the email would be enough. I suspect the answer is no…
  • I would be fine handling this entirely in the client, but GitHub can only callback to a public route so I can’t get the token to the client side of a private plugin page…

Thanks for any help!

This topic has been created from a Discord post (1214241003779784734) to give it more visibility.
It will be on Read-Only mode here.
Join the conversation on Discord

You can access whatever inside of controller

You don’t have an auth user, since there is no user

But if you pass some params along with payload you can use this easily

Thanks for your answer <@632956853122236457> !

You can access whatever inside of controller
That doesn’t seem to be the case for controllers that are handlers of a public route. For example:

// plugin/server/routes/index.js
 module.exports = [{
    method: 'GET',
    path: '/action',
    handler: 'example.action', // This controller can access whatever
  },
  {
    method: 'GET',
    path: '/callback',
    handler: 'example.callback', // This controller can't access a lot of stuff
    config: {
      auth: false  // This is what makes this route callable from the external authentication service (GitHub)
    }
  }]
// plugin/server/controllers/example.js
module.exports = ({ strapi }) => ({
  action(ctx) {
    console.log(ctx.state.user) // returns user data
  },
  callback(ctx) {
    console.log(ctx.state.user) // is null
  }
})

But if you pass data along with token you can use this easily
Since GitHub is in charge of redirecting to /callback, I’m not sure how I could pass data along with the token.

I ended up solving this by creating a global middleware to intercept the request from GitHub, masking the request was going to a page rather than a route (not fully sure why this is necessary, but it seems to be), and redirect to the plugin homepage with the token in the URL query. I am not sure this is the best answer, but it seems to do the job.

strapi.server.use(async (ctx, next) => {
  // intercept the request to the callback route
  if (ctx.req.url.startsWith('/my-plugin/callback')){
    const {code} = ctx.query; // store the query
    ctx.request.url = "/admin"; // adjust the request to make it seem like it's going to a page instead of a route
    ctx.redirect(`/admin/plugins/my-plugin?code=${code}`) // redirect and pass the code
  }
  await next()
})

You don’t have ctx.user cause it’s undefined

But you can do strapi.db.query(‘plugin::user-permissions.user’).findOne({where: … })