Profile image with Google Auth Provider

Hi!
Is there a way to get the profile picture of the user that use Google Auth?

I think you should need to review what is the payload which comes with the provider and handle it on the Strapi controller; I don’t think there is a easy way to do that.

1 Like

So here is the solution :

1 - Add this file as Providers.js

"use strict";

/**
 * Module dependencies.
 */

// Public node modules.
const _ = require("lodash");
const request = require("request");

// Purest strategies.
const purest = require("purest")({ request });
const purestConfig = require("@purest/providers");
const { getAbsoluteServerUrl } = require("strapi-utils");
const jwt = require("jsonwebtoken");

const axios = require('axios').default;

/**
 * Connect thanks to a third-party provider.
 *
 *
 * @param {String}    provider
 * @param {String}    access_token
 *
 * @return  {*}
 */

const connect = (provider, query) => {
  const access_token = query.access_token || query.code || query.oauth_token;

  return new Promise((resolve, reject) => {
    if (!access_token) {
      return reject([null, { message: "No access_token." }]);
    }

    // Get the profile.
    getProfile(provider, query, async (err, profile) => {
      if (err) {
        return reject([null, err]);
      }

      // We need at least the mail.
      if (!profile.email) {
        return reject([null, { message: "Email was not available." }]);
      }

      try {
        const users = await strapi.query("user", "users-permissions").find({
          email: profile.email,
        });

        const advanced = await strapi
          .store({
            environment: "",
            type: "plugin",
            name: "users-permissions",
            key: "advanced",
          })
          .get();

        const user = _.find(users, { provider });

        if (_.isEmpty(user) && !advanced.allow_register) {
          return resolve([
            null,
            [{ messages: [{ id: "Auth.advanced.allow_register" }] }],
            "Register action is actually not available.",
          ]);
        }

        if (!_.isEmpty(user)) {
          return resolve([user, null]);
        }

        if (
          !_.isEmpty(_.find(users, (user) => user.provider !== provider)) &&
          advanced.unique_email
        ) {
          return resolve([
            null,
            [{ messages: [{ id: "Auth.form.error.email.taken" }] }],
            "Email is already taken.",
          ]);
        }

        // Retrieve default role.
        const defaultRole = await strapi
          .query("role", "users-permissions")
          .findOne({ type: advanced.default_role }, []);

        // Create the new user.
        const params = _.assign(profile, {
          provider: provider,
          role: defaultRole.id,
          confirmed: true,
        });

        const createdUser = await strapi
          .query("user", "users-permissions")
          .create(params);

        return resolve([createdUser, null]);
      } catch (err) {
        reject([null, err]);
      }
    });
  });
};

/**
 * Helper to get profiles
 *
 * @param {String}   provider
 * @param {Function} callback
 */

const getProfile = async (provider, query, callback) => {
  const access_token = query.access_token || query.code || query.oauth_token;

  const grant = await strapi
    .store({
      environment: "",
      type: "plugin",
      name: "users-permissions",
      key: "grant",
    })
    .get();

  switch (provider) {
    case "discord": {
      const discord = purest({
        provider: "discord",
        config: {
          discord: {
            "https://discordapp.com/api/": {
              __domain: {
                auth: {
                  auth: { bearer: "[0]" },
                },
              },
              "{endpoint}": {
                __path: {
                  alias: "__default",
                },
              },
            },
          },
        },
      });
      discord
        .query()
        .get("users/@me")
        .auth(access_token)
        .request((err, res, body) => {
          if (err) {
            callback(err);
          } else {
            console.log(body);
            callback(null, {
              username: body.id,
              email: body.id + "@example.com",
            });
          }
        });
      break;
    }
    case "cognito": {
      // get the id_token
      const idToken = query.id_token;
      // decode the jwt token
      const tokenPayload = jwt.decode(idToken);
      if (!tokenPayload) {
        callback(new Error("unable to decode jwt token"));
      } else {
        callback(null, {
          username: tokenPayload["cognito:username"],
          email: tokenPayload.email,
        });
      }
      break;
    }
    case "facebook": {
      const facebook = purest({
        provider: "facebook",
        config: purestConfig,
      });

      facebook
        .query()
        .get('me?fields=id,name,email')
        .auth(access_token)
        .request((err, res, body) => {
          // If no email, create a mimic email based on id
          let facebookEmail = body.email;
          if (!facebookEmail) {
            facebookEmail = body.id.concat('@facebook.com');
          }
          if (err) {
            callback(err);
          } else {
            callback(null, {
              // Changed this to ID instead of name
              username: body.name,
              providerId: body.id,
              email: facebookEmail,
            });
          }
        });
      break;
    }
    case "google": {
      const instance = axios.create({
        baseURL: 'https://www.googleapis.com/oauth2/v3',
        headers: {'Authorization': 'Bearer ' + access_token }
      });

      instance.get('userinfo').then(x =>  {
        callback(null, {
          username: x.data.given_name,
          providerId: x.data.sub,
          email: x.data.email,
        });
      }, err => callback(err));
      break;
    }
    case "github": {
      const github = purest({
        provider: "github",
        config: purestConfig,
        defaults: {
          headers: {
            "user-agent": "strapi",
          },
        },
      });

      github
        .query()
        .get("user")
        .auth(access_token)
        .request((err, res, userbody) => {
          if (err) {
            return callback(err);
          }

          // This is the public email on the github profile
          if (userbody.email) {
            return callback(null, {
              username: userbody.login,
              email: userbody.email,
            });
          }

          // Get the email with Github's user/emails API
          github
            .query()
            .get("user/emails")
            .auth(access_token)
            .request((err, res, emailsbody) => {
              if (err) {
                return callback(err);
              }

              return callback(null, {
                username: userbody.login,
                email: Array.isArray(emailsbody)
                  ? emailsbody.find((email) => email.primary === true).email
                  : null,
              });
            });
        });
      break;
    }
    case "microsoft": {
      const microsoft = purest({
        provider: "microsoft",
        config: purestConfig,
      });

      microsoft
        .query()
        .get("me")
        .auth(access_token)
        .request((err, res, body) => {
          if (err) {
            callback(err);
          } else {
            callback(null, {
              username: body.userPrincipalName,
              email: body.userPrincipalName,
            });
          }
        });
      break;
    }
    case "twitter": {
      const twitter = purest({
        provider: "twitter",
        config: purestConfig,
        key: grant.twitter.key,
        secret: grant.twitter.secret,
      });

      twitter
        .query()
        .get("account/verify_credentials")
        .auth(access_token, query.access_secret)
        .qs({ screen_name: query["raw[screen_name]"], include_email: "true" })
        .request((err, res, body) => {
          if (err) {
            callback(err);
          } else {
            callback(null, {
              username: body.screen_name,
              email: body.email,
            });
          }
        });
      break;
    }
    case "instagram": {
      const instagram = purest({
        provider: "instagram",
        key: grant.instagram.key,
        secret: grant.instagram.secret,
        config: purestConfig,
      });

      instagram
        .query()
        .get("me")
        .qs({ access_token, fields: "id,username" })
        .request((err, res, body) => {
          if (err) {
            callback(err);
          } else {
            callback(null, {
              username: body.username,
              email: `${body.username}@strapi.io`, // dummy email as Instagram does not provide user email
              // This is really bad, who the heck did this???
            });
          }
        });
      break;
    }
    case "vk": {
      const vk = purest({
        provider: "vk",
        config: purestConfig,
      });

      vk.query()
        .get("users.get")
        .qs({ access_token, id: query.raw.user_id, v: "5.122" })
        .request((err, res, body) => {
          if (err) {
            callback(err);
          } else {
            callback(null, {
              username: `${body.response[0].last_name} ${body.response[0].first_name}`,
              email: query.raw.email,
            });
          }
        });
      break;
    }
    case "twitch": {
      const twitch = purest({
        provider: "twitch",
        config: {
          twitch: {
            "https://api.twitch.tv": {
              __domain: {
                auth: {
                  headers: {
                    Authorization: "Bearer [0]",
                    "Client-ID": "[1]",
                  },
                },
              },
              "helix/{endpoint}": {
                __path: {
                  alias: "__default",
                },
              },
              "oauth2/{endpoint}": {
                __path: {
                  alias: "oauth",
                },
              },
            },
          },
        },
      });

      twitch
        .get("users")
        .auth(access_token, grant.twitch.key)
        .request((err, res, body) => {
          if (err) {
            callback(err);
          } else {
            callback(null, {
              username: body.data[0].login,
              email: body.data[0].email,
            });
          }
        });
      break;
    }
    case "linkedin": {
      const linkedIn = purest({
        provider: "linkedin",
        config: {
          linkedin: {
            "https://api.linkedin.com": {
              __domain: {
                auth: [{ auth: { bearer: "[0]" } }],
              },
              "[version]/{endpoint}": {
                __path: {
                  alias: "__default",
                  version: "v2",
                },
              },
            },
          },
        },
      });
      try {
        const getDetailsRequest = () => {
          return new Promise((resolve, reject) => {
            linkedIn
              .query()
              .get("me")
              .auth(access_token)
              .request((err, res, body) => {
                if (err) {
                  return reject(err);
                }
                resolve(body);
              });
          });
        };

        const getEmailRequest = () => {
          return new Promise((resolve, reject) => {
            linkedIn
              .query()
              .get("emailAddress?q=members&projection=(elements*(handle~))")
              .auth(access_token)
              .request((err, res, body) => {
                if (err) {
                  return reject(err);
                }
                resolve(body);
              });
          });
        };

        const { localizedFirstName } = await getDetailsRequest();
        const { elements } = await getEmailRequest();
        const email = elements[0]["handle~"];

        callback(null, {
          username: localizedFirstName,
          email: email.emailAddress,
        });
      } catch (err) {
        callback(err);
      }
      break;
    }
    case "reddit": {
      const reddit = purest({
        provider: "reddit",
        config: purestConfig,
        defaults: {
          headers: {
            "user-agent": "strapi",
          },
        },
      });

      reddit
        .query("auth")
        .get("me")
        .auth(access_token)
        .request((err, res, body) => {
          if (err) {
            callback(err);
          } else {
            callback(null, {
              username: body.name,
              email: `${body.name}@strapi.io`, // dummy email as Reddit does not provide user email
            });
          }
        });
      break;
    }
    case "auth0": {
      const purestAuth0Conf = {};
      purestAuth0Conf[`https://${grant.auth0.subdomain}.auth0.com`] = {
        __domain: {
          auth: {
            auth: { bearer: "[0]" },
          },
        },
        "{endpoint}": {
          __path: {
            alias: "__default",
          },
        },
      };
      const auth0 = purest({
        provider: "auth0",
        config: {
          auth0: purestAuth0Conf,
        },
      });

      auth0
        .get("userinfo")
        .auth(access_token)
        .request((err, res, body) => {
          if (err) {
            callback(err);
          } else {
            const username =
              body.username ||
              body.nickname ||
              body.name ||
              body.email.split("@")[0];
            const email =
              body.email || `${username.replace(/\s+/g, ".")}@strapi.io`;

            callback(null, {
              username,
              email,
            });
          }
        });
      break;
    }
    case "cas": {
      const provider_url = "https://" + _.get(grant["cas"], "subdomain");
      const cas = purest({
        provider: "cas",
        config: {
          cas: {
            [provider_url]: {
              __domain: {
                auth: {
                  auth: { bearer: "[0]" },
                },
              },
              "{endpoint}": {
                __path: {
                  alias: "__default",
                },
              },
            },
          },
        },
      });
      cas
        .query()
        .get("oidc/profile")
        .auth(access_token)
        .request((err, res, body) => {
          if (err) {
            callback(err);
          } else {
            // CAS attribute may be in body.attributes or "FLAT", depending on CAS config
            const username = body.attributes
              ? body.attributes.strapiusername || body.id || body.sub
              : body.strapiusername || body.id || body.sub;
            const email = body.attributes
              ? body.attributes.strapiemail || body.attributes.email
              : body.strapiemail || body.email;
            if (!username || !email) {
              strapi.log.warn(
                "CAS Response Body did not contain required attributes: " +
                  JSON.stringify(body)
              );
            }
            callback(null, {
              username,
              email,
            });
          }
        });
      break;
    }
    default:
      callback(new Error("Unknown provider."));
      break;
  }
};

const buildRedirectUri = (provider = "") =>
  `${getAbsoluteServerUrl(strapi.config)}/connect/${provider}/callback`;

module.exports = {
  connect,
  buildRedirectUri,
};

2 - Add boostrap.js

'use strict';

/**
 * 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.
 */
const _ = require('lodash');
const uuid = require('uuid/v4');

module.exports = async () => {
  const pluginStore = strapi.store({
    environment: '',
    type: 'plugin',
    name: 'users-permissions',
  });

  const grantConfig = {
    email: {
      enabled: true,
      icon: 'envelope',
    },
    discord: {
      enabled: false,
      icon: 'discord',
      key: '',
      secret: '',
      callback: `${strapi.config.server.url}/auth/discord/callback`,
      scope: ['identify', 'email'],
    },
    facebook: {
      enabled: false,
      icon: 'facebook-square',
      key: '',
      secret: '',
      callback: `${strapi.config.server.url}/auth/facebook/callback`,
      scope: ['email'],
    },
    google: {
      enabled: false,
      icon: 'google',
      key: '',
      secret: '',
      callback: `${strapi.config.server.url}/auth/google/callback`,
      scope: ['email', 'profile'],
    },
    github: {
      enabled: false,
      icon: 'github',
      key: '',
      secret: '',
      callback: `${strapi.config.server.url}/auth/github/callback`,
      scope: ['user', 'user:email'],
    },
    microsoft: {
      enabled: false,
      icon: 'windows',
      key: '',
      secret: '',
      callback: `${strapi.config.server.url}/auth/microsoft/callback`,
      scope: ['user.read'],
    },
    twitter: {
      enabled: false,
      icon: 'twitter',
      key: '',
      secret: '',
      callback: `${strapi.config.server.url}/auth/twitter/callback`,
    },
    instagram: {
      enabled: false,
      icon: 'instagram',
      key: '',
      secret: '',
      callback: `${strapi.config.server.url}/auth/instagram/callback`,
    },
    vk: {
      enabled: false,
      icon: 'vk',
      key: '',
      secret: '',
      callback: `${strapi.config.server.url}/auth/vk/callback`,
      scope: ['email'],
    },
    twitch: {
      enabled: false,
      icon: 'twitch',
      key: '',
      secret: '',
      callback: `${strapi.config.server.url}/auth/twitch/callback`,
      scope: ['user:read:email'],
    },
  };
  const prevGrantConfig = (await pluginStore.get({ key: 'grant' })) || {};
  // store grant auth config to db
  // when plugin_users-permissions_grant is not existed in db
  // or we have added/deleted provider here.
  if (!prevGrantConfig || !_.isEqual(_.keys(prevGrantConfig), _.keys(grantConfig))) {
    // merge with the previous provider config.
    _.keys(grantConfig).forEach(key => {
      if (key in prevGrantConfig) {
        grantConfig[key] = _.merge(grantConfig[key], prevGrantConfig[key]);
      }
    });
    await pluginStore.set({ key: 'grant', value: grantConfig });
  }

  if (!(await pluginStore.get({ key: 'email' }))) {
    const value = {
      reset_password: {
        display: 'Email.template.reset_password',
        icon: 'sync',
        options: {
          from: {
            name: 'Administration Panel',
            email: 'no-reply@strapi.io',
          },
          response_email: '',
          object: 'Reset password',
          message: `<p>We heard that you lost your password. Sorry about that!</p>

<p>But don’t worry! You can use the following link to reset your password:</p>
<p><%= URL %>?code=<%= TOKEN %></p>

<p>Thanks.</p>`,
        },
      },
      email_confirmation: {
        display: 'Email.template.email_confirmation',
        icon: 'check-square',
        options: {
          from: {
            name: 'Administration Panel',
            email: 'no-reply@strapi.io',
          },
          response_email: '',
          object: 'Account confirmation',
          message: `<p>Thank you for registering!</p>

<p>You have to confirm your email address. Please click on the link below.</p>

<p><%= URL %>?confirmation=<%= CODE %></p>

<p>Thanks.</p>`,
        },
      },
    };

    await pluginStore.set({ key: 'email', value });
  }

  if (!(await pluginStore.get({ key: 'advanced' }))) {
    const value = {
      unique_email: true,
      allow_register: true,
      email_confirmation: false,
      email_confirmation_redirection: `${strapi.config.admin.url}/admin`,
      email_reset_password: `${strapi.config.admin.url}/admin`,
      default_role: 'authenticated',
    };

    await pluginStore.set({ key: 'advanced', value });
  }

  await strapi.plugins['users-permissions'].services.userspermissions.initialize();

  if (!_.get(strapi.plugins['users-permissions'], 'config.jwtSecret')) {
    const jwtSecret = uuid();
    _.set(strapi.plugins['users-permissions'], 'config.jwtSecret', jwtSecret);

    strapi.reload.isWatching = false;

    await strapi.fs.writePluginFile(
      'users-permissions',
      'config/jwt.js',
      `module.exports = {\n  jwtSecret: process.env.JWT_SECRET || '${jwtSecret}'\n};`
    );

    strapi.reload.isWatching = true;
  }
};

Thank you very much man, very useful.

Hello, just starting up my first Strapi project and I actually need first and last name from the cognito user.
I have a few question:
Which strapi version is the code snippet from?
Do I add the extensions folder structure by hand or is there some documentation on it that helped you?

Thanks a bunch!

Hi mate,

Twas on v3 may you have a look here :

Someone give a v4 exemple code. I also notice a Google-auth plugin on v4 marketplace, maybe it could bé helpful