Simple PHP OAuth2 server to SSO with WordPress account

The research is based on awesome PHP OAuth server solution proposed by Justin Greer. The idea under the PoC was to create simple php OAuth2 server to SSO with WordPress user account.

To do so, we need WordPress instance up and running. It might be Local installation as well to make it simple. We will create w PHP plugin to host OAuth2 API server and register a OAuth2 clients with ClientID/ClientSecret. To test PoC I will leverage Node,js application hosted on cloud server configured as a Generic OAuth2 client. The client be able to login with WordPress user account to Node.js application. The must-have feature is, it should be possible to map WP role to Node.js app user group as part of authorization process.

Flow diagram

The flow diagram below shows how exactly authorization process looks like:

simple php OAuth2 server to sso with wordpress

The process consist of three main parts:

  1. Generic OAuth2 client calls /oauth/authorize endpoint, passes client_id and a nonce state parameter during authentication to protect against CSRF attacks.
  2. OAuth2 server generates code and calls back.
  3. The client asks for token sending code, grant_type which is ‘authorization_code’, client_id and client_secret to authorize against the server.
  4. Server generates short living token.
  5. Client calls server to get user data from WordPress database.
  6. Server retrieves logged user information and additional meta data like role
  7. OAuth2 server returns back user_claim
  8. Client maps user information and create or update user in Node.js database.

Configuration

OAuth2 server plugin for WordPress is available here on my GitHub. The essential part is API:

switch($method){
	
	case 'authorize':
        ...
        if ( !is_user_logged_in() ) {
	        wp_redirect( site_url() . '/wp-login/?sso_redirect='.$_GET['client_id'].'&state='.$_GET['state']);
		    exit();
	    }
        ...
    case 'request_token':
        ...
    case 'request_access':
        ...
           $info = $wpdb->get_row("SELECT 
			u.ID
			, u.user_login
			, u.user_nicename
			, u.user_email
			, u.user_url
			, u.user_registered
			, u.user_status
			, u.display_name
			, m.meta_value AS role 
			FROM {$wpdb->prefix}users AS u 
			JOIN {$wpdb->prefix}usermeta AS m ON u.ID = m.user_id WHERE u.ID = ".$user_id." 
				AND m.meta_key = '{$wpdb->prefix}capabilities'");

	        // retrive user role, if not assign free role
	        $info->role = explode(" ", preg_match('/"(.*?)"/s', $info->role, $match) == 1 ? $match[1] : "free");

To use the plugin, pull repository and zip it. Go to WordPress admin panel, then plugins and upload new:

‘Provider’ menu entry should appear, open and generate a new client:

Do not forget to specify callback_url. In my case, I have already added generic OAuth2 client to my Wiki.js installation and generate callback url.

Client configuration as below:

Proof of concept

First of all, I have been registered a new user on my WordPress site:

Using separate account, logged in the nearly created account. then open node.js site login page and log with generic OAuth2 client.

First, it will generate a new temporary token in WP database:

Using the token we are able to get user_claim with Postman:

All, we need now is to specify claims as shown below in OAuth2 client strategy configuration panel:

Set up Node.js (or whatever application you use) OAuth2 client strategy using callback_url generated above. I am using passport module for node.js. Strategy will looks like:

const OAuth2Strategy = require('passport-oauth2').Strategy

module.exports = {
  init (passport, conf) {
    var client = new OAuth2Strategy({
      authorizationURL: conf.authorizationURL,
      tokenURL: conf.tokenURL,
      clientID: conf.clientId,
      clientSecret: conf.clientSecret,
      userInfoURL: conf.userInfoURL,
      callbackURL: conf.callbackURL,
      passReqToCallback: true,
      scope: conf.scope,
      state: conf.enableCSRFProtection
    }, async (req, accessToken, refreshToken, profile, cb) => {
      try {
        const user = await WIKI.models.users.processProfile({
          providerKey: req.params.strategy,
          profile: {
            ...profile,
            id: _.get(profile, conf.userIdClaim),
            displayName: _.get(profile, conf.displayNameClaim, '???'),
            email: _.get(profile, conf.emailClaim)
          }
        })
        if (conf.mapGroups) {
          const groups = _.get(profile, conf.groupsClaim)
          if (groups && _.isArray(groups)) {
            const currentGroups = (await user.$relatedQuery('groups').select('groups.id')).map(g => g.id)
            const expectedGroups = Object.values(WIKI.auth.groups).filter(g => groups.includes(g.name)).map(g => g.id)
            for (const groupId of _.difference(expectedGroups, currentGroups)) {
              await user.$relatedQuery('groups').relate(groupId)
            }
            for (const groupId of _.difference(currentGroups, expectedGroups)) {
              await user.$relatedQuery('groups').unrelate().where('groupId', groupId)
            }
          }
        }
        cb(null, user)
      } catch (err) {
        cb(err, null)
      }
    })

    client.userProfile = function (accesstoken, done) {
      this._oauth2._useAuthorizationHeaderForGET = !conf.useQueryStringForAccessToken
      this._oauth2.get(conf.userInfoURL, accesstoken, (err, data) => {
        if (err) {
          return done(err)
        }
        try {
          data = JSON.parse(data)
        } catch (e) {
          return done(e)
        }
        done(null, data)
      })
    }
    passport.use(conf.key, client)
  },
  logout (conf) {
    if (!conf.logoutURL) {
      return '/'
    } else {
      return conf.logoutURL
    }
  }
}

After login successful, new user should be added as expected:

Appropriate user group SHOULD BE created in Wiki.js before. In my case I have create ‘free‘ group both in WP and Wiki.js

That is mostly it! I hope it was useful research done.

For more interesting content visit my #CyberTechTalk blog or follow me on Twitter.

Be an ethical, save your privacy!

subscribe to newsletter

and receive weekly update from our blog

By submitting your information, you're giving us permission to email you. You may unsubscribe at any time.

Leave a Comment

Discover more from #cybertechtalk

Subscribe now to keep reading and get access to the full archive.

Continue reading