Jump to content

GMAIL Login for Processwire


quickjeff
 Share

Recommended Posts

Hi Guys, 

So after reading multiple threads and searching for a solution required for a project, I decided to build a module to enable users to login to the CMS via their gmail account and hopefully the module can also be altered for LinkedIn and other Social accounts.  Unfortunately the Facebook module does give you a Facebook ID and assigns the Facebook Role however I would like to assign the role, get the users email and create the user if its not in the system.  

I have basically taken the Facebook Login Module created by apeisa and manipulated the module to somewhat work with GMAIL.

What it does now:  It allows you to login but doesn't pull any data from GMAIL. Therefore it automatically logins in to the first account created in ProcessWire, but oddly changes the password, Im sure its due to the line of code in the module that is set to generate a random password via sha1.

What I need it to do: If the user isn't in the system, get the gmail email, add the gmail email to the email field in ProcessWire, assign the gmail role or whatever role I want to auto assign it and log them in.

​My resources from Google: https://developers.google.com/accounts/docs/OAuth2

Please note, I will be updating the code as I make progress but please give input or help if you can. As always, thank you so much to everyone for their input and help, especially apeisa and craig a rodway for giving some direction initially.

Here is what I have so far:

<?php

class GoogleLogin extends WireData implements Module, ConfigurableModule {

	const name = 'google-login';

       //the google ID isn't used in Gmail so I am sure this will go away. 
	const fieldName = 'google_id';

	public static function getModuleInfo() {
		return array(
		 "title" => "Google Login for Website",
		 "version" => 001,
		 "summary" => "Allows users to authenticate through their GMAIL account.",
		 "autoload" => false
		);
	}

	public function init() {

	}

	public function execute() {

		// Prevent association of Google Account to an existing user
		if ($this->user->isLoggedin()) {
			echo "Already logged in.";
			return;
		}

		$client_id = $this->googleAppId;
		$app_secret = $this->googleAppSecret;
		$redirect_uri = $this->page->httpUrl;

		$code = $_REQUEST["code"];

		if(empty($code)) {
			$_SESSION['state'] = md5(uniqid(rand(), TRUE)); //CSRF protection
			$dialog_url = "https://accounts.google.com/o/oauth2/auth?client_id=" . $client_id . "&redirect_uri=" . $redirect_uri . "&state=" . $_SESSION['state'] . "&scope=profile email&response_type=code";
			echo("<script> top.location.href='" . $dialog_url . "'</script>");
		}

		if($_SESSION['state'] && ($_SESSION['state'] === $_REQUEST['state'])) {
			$token_url = "https://accounts.google.com/o/oauth2/auth/access_token?"
				. "client_id=" . $client_id . "&redirect_uri=" . urlencode($redirect_uri)
				. "&client_secret=" . $app_secret . "&code=" . $code;

			$response = file_get_contents($token_url);
			$params = null;
			parse_str($response, $params);

			$access_url = "https://accounts.google.com/o/oauth2/auth/user?access_token=" . $params['access_token'];

			// Add stream context
			$options  = array('http' => array('user_agent'=> $_SERVER['HTTP_USER_AGENT']));
			$context  = stream_context_create($options);

			$ghUserData = json_decode(file_get_contents($access_url, false, $context));

			$this->processLogin($ghUserData);
		}
		else {
			echo("The state does not match. You may be a victim of CSRF.");
		}
		
	}

	public function processLogin($ghUserData) {
		$id = $ghUserData->id;
		$u = $this->users->get("google_id=$id");

		// First we create random pass to use in login
		$uniqid = uniqid();
		$pass = sha1($uniqid . $id . $ghUserData->updated_at);

		// User has logged in earlier with Google id, great news let's login
		if ($u->id) {
			$u->of(false);
			$u->pass = $pass;
			$u->addRole(self::name);
			$u->save();
		}

		// User has not logged in before and autogenerate is on
		else if (!$this->disableAutogenerate) {
			$name = $this->sanitizer->pageName($ghUserData->name, true);
			$u = $this->users->get("name=$name");

			// All seems to be fine, let's create the user
			$u = new User;
			$u->name = $name;
			$u->google_id = $ghUserData->id;
			$u->pass = $pass;
			$u->addRole(self::name);
			$u->save();
		} else {
			echo $this->renderCreateUserForm();
		}

		$this->session->login($u->name, $pass);
		$p = $this->pages->get($this->redirectPage);
		if (!$p->id) {
			$p = $this->pages->get(1);
		}
		$this->session->redirect($p->httpUrl);
	}

	public function renderCreateUserForm() {

	}

	static public function getModuleConfigInputfields(Array $data) {
		$fields = new InputfieldWrapper();

		// since this is a static function, we can't use $this->modules, so get them from the global wire() function
		$modules = wire('modules');

		$field = $modules->get("InputfieldText");
		$field->attr('name', 'googleAppId');
		$field->attr('value', $data['googleAppId']);
		$field->label = "Google App Id";
		$field->description = 'Client Id for your project. You can create one from here: https://console.developers.google.com';
		$fields->add($field);

		$field = $modules->get("InputfieldText");
		$field->attr('name', 'googleAppSecret');
		$field->attr('value', $data['googleAppSecret']);
		$field->label = "Google App Secret";
		$field->description = 'Client Secret for your project. Available in your project console here: https://console.developers.google.com';
		$fields->add($field);

		/*
		$field = $modules->get("InputfieldCheckbox");
		$field->attr('name', 'disableAutogenerate');
		$field->attr('value', 1);
		$field->attr('checked', empty($data['disableAutogenerate']) ? '' : 'checked');
		$field->label = "Don't set username automatically, but let the user choose username when doing the first login";
		$fields->add($field);
		*/

		$field = $modules->get("InputfieldPageListSelect");
		$field->attr('name', 'redirectPage');
		$field->attr('value', $data['redirectPage']);
		$field->label = "Page where user is redirected after succesful login";
		$fields->add($field);

		return $fields;

	}

	public function install() {

		$name = self::name;
		$fieldName = self::fieldName;

		$page = $this->pages->get("/$name/");
		if($page->id) throw new WireException("There is already a page installed called '/$name/'");

		$template = $this->templates->get($name);
		if($template) throw new WireException("There is already a template installed called '$name'");

		$fieldgroup = $this->fieldgroups->get($name);
		if($fieldgroup) throw new WireException("There is already a fieldgroup installed called '$name'");

		$field = $this->fields->get($fieldName);
		if($field) throw new WireException("There is already a field installed called '$fieldName'");

		$role = $this->roles->get($name);
		if (!$role->id) {
			$this->roles->add($name);
			$this->message("Create role called $name");
		}

		$fieldgroup = new Fieldgroup();
		$fieldgroup->name = $name;
		$title = $this->fields->get('title');
		if($title) $fieldgroup->add($title);
		$fieldgroup->save();

		$template = new Template();
		$template->name = $name;
		$template->fieldgroup = $fieldgroup;
		$template->allowPageNum = 1;
		$template->save();
		$this->message("Installed template $name");

		$page = new Page();
		$page->template = $template;
		$page->parent = '/';
		$page->name = $name;
		$page->title = "Google Login";
		$page->addStatus(Page::statusHidden);
		$page->save();
		$this->message("Installed page $page->path");

		$basename = $name . ".php";
		$src = $this->config->paths->SessionGoogleLogin . $basename;
		$dst = $this->config->paths->templates . $basename;
		if(@copy($src, $dst)) {
			$this->message("Installed template file $basename");
		}	else {
			$this->error("Templates directory is not writable so we were unable to auto-install the $basename template file.");
			$this->error("To complete the installation please copy $basename from $src to $dst");
		}

		// Create hidden inputfield
		$input = new InputfieldText;
		$input->set('collapsed', Inputfield::collapsedHidden);

		// Create field called Google and set details and inputfield
		$f = new Field();
		$f->type = $this->modules->get("FieldtypeText");
		$f->name = $fieldName;
		$f->label = 'Google ID';
		$f->description = 'Stores Google id for user';
		$f->inputfield = $input;
		$f->save();

		// Add the field to user fieldgroup (basically means user template in this context)
		$fg = $this->fieldgroups->get('user');
		$fg->add($f);
		$fg->save();
	}

	public function uninstall() {

	}
}

  • Like 4
Link to comment
Share on other sites

I see this approach is also loosely based on my GitHub Login "fork" of apeisas FB module :)

Can you debug what is returned from...

     $ghUserData = json_decode(file_get_contents($access_url, false, $context));

...and if $ghUserData contains the Google Mail address in the first place? That was the approach, to get a value (above its the GitHub Id) from the JSON and persist it on a certain user as a field value. Theoretically this should work as well with the email, which would then become $u->email.

Also, this...

// Add stream context
			$options  = array('http' => array('user_agent'=> $_SERVER['HTTP_USER_AGENT']));
			$context  = stream_context_create($options);

...was somehow necessary for GH, but not in apeisas original module.

It's quite interesting how often apeisas module appeared here lately. Screams for a generic OAuth2 module, doesn't it? :)

// edit:

It seems that the "grabs first user, changes their password, logs them in" phenomenon has its cause here:

public function processLogin($ghUserData) {
        $id = $ghUserData->id; // (1)
        $u = $this->users->get("google_id=$id"); // (2)

        // First we create random pass to use in login
        $uniqid = uniqid();
        $pass = sha1($uniqid . $id . $ghUserData->updated_at);

        // User has logged in earlier with Google id, great news let's login
        if ($u->id) { // (3)
            $u->of(false);
            $u->pass = $pass; // (4)
            $u->addRole(self::name);
            $u->save();
        }

Assumptions:

(1) is empty/null

(2) possibly translates as ->get() = ->first(), so returns first user

(3) this is true, but the wrong user id

(4) overwrite first users password

// edit2:

Your thread got me tweaking my GitHub Login module a little bit. I learned that I can only retrieve the (GitHub) user's email if its set to 'public'. Maybe thats similar in Google's API - either you aren't able to get the email at all, or it has to explicitly set as public. However, I think you have to solve said "phnomenon" first.

Edited by marcus
  • Like 2
Link to comment
Share on other sites

@marcus

Totally agree, a generic OAuth module would be nice. You are also correct with the Github example you did, which I strongly thank you for!!! It's a combination of that as well as the Twitter version. Also, tested another version I created with a combination of all, using LinkedIn, I get the same issues. It logs into the original users account when ProcessWire was first setup changing the password, as if it was overwriting the users account. In Googles case, it says you can view certain items including the email but not sure if you can get it. In LinkedIn the documentation states you can get the email.

Hoping to make some breakthrough fairly soon.

:-(

Link to comment
Share on other sites

  • 2 years later...

I working model of a Google account auth would be great. I really hope you make some breakthrough fairly soon. Also if you need hlp, there are a bunch of guides and tutorials on various Gmail (link removed by admin) Account and google account issues.

Edited by diogo
Link removed — doesn't seem relevant
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...