Last active
September 5, 2019 09:10
-
-
Save igorbenic/12c7c4f1fc7ec8895660e7f7a02f1a95 to your computer and use it in GitHub Desktop.
Headless WordPress: Logging with Google and JWT | https://www.ibenic.com/headless-wordpress-google-jwt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Headless\REST_API; | |
use \Firebase\JWT\JWT; | |
class Login extends REST_API { | |
// ... | |
/** | |
* Login with Google. | |
* | |
* @param \WP_REST_Request $request | |
*/ | |
public function login_google( $request ) { | |
header( 'Access-Control-Allow-Origin: *' ); | |
$params = $request->get_params(); | |
$token = isset( $params['token'] ) ? $params['token'] : false; | |
if ( ! $token ) { | |
return new \WP_Error( 'no-token', __( 'No token received from Google', 'vivant' ) ); | |
} | |
if ( ! class_exists( 'Google_Client' ) ) { | |
include_once plugin_dir_path( HEADLESS_FILE ) . '/vendor/google/apiclient/src/Google/Client.php'; // change path as needed | |
} | |
$client = new \Google_Client( [ 'client_id' => $this->googleClient ] ); // Specify the CLIENT_ID of the app that accesses the backend | |
try { | |
$payload = $client->verifyIdToken( $token ); | |
if ( $payload ) { | |
$google_user_id = $payload['sub']; | |
// If request specified a G Suite domain: | |
//$domain = $payload['hd']; | |
$email = $payload['email']; | |
$name = $payload['name']; | |
$user = $this->find_user_by( 'google_id', $google_user_id ); | |
if ( is_a( $user, 'WP_User' ) ) { | |
update_user_meta( $user->ID, '_google_token', $token ); | |
return $this->create_token( $user ); | |
} else { | |
$user = $this->find_user_by( 'email', $email ); | |
if ( ! $user ) { | |
$username = str_replace( ' ', '', $name ); | |
$user_id = $this->create_user( $username, $email ); | |
$first_name = isset( $payload['given_name'] ) ? $payload['given_name'] : $name; | |
$last_name = isset( $payload['family_name'] ) ? $payload['family_name'] : ''; | |
if ( is_wp_error( $user_id ) ) { | |
return $user_id; | |
} | |
$user = new \WP_User( $user_id ); | |
wp_update_user( array( 'ID' => $user_id, | |
'display_name' => $name, | |
'first_name' => $first_name, | |
'last_name' => $last_name | |
) ); | |
} | |
add_user_meta( $user->ID, '_google_user_id', $google_user_id ); | |
add_user_meta( $user->ID, '_google_token', $token ); | |
return $this->create_token( $user ); | |
} | |
} else { | |
return new \WP_Error( 'invalid-token', __( 'Token is not valid', 'vivant' ) ); | |
} | |
} catch ( \Exception $e ) { | |
return new \WP_Error( $e->getCode(), $e->getMessage() ); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Headless\REST_API; | |
use \Firebase\JWT\JWT; | |
class Login extends REST_API { | |
/** | |
* Client | |
* | |
* @var string | |
*/ | |
protected $googleClient = 'YOUR_GOOGLE_CLIENT_ID'; | |
/** | |
* @var string | |
*/ | |
protected $path = 'login'; | |
/** | |
* Experience constructor. | |
*/ | |
public function __construct() { | |
$this->prepare_google(); | |
$this->add_route( 'google', array( | |
'methods' => \WP_REST_Server::CREATABLE, | |
'callback' => array( $this, 'login_google' ), | |
)); | |
} | |
/** | |
* @param $key | |
* @param $value | |
* | |
* @return false|\WP_User | |
*/ | |
protected function find_user_by( $key, $value ) { | |
$user = false; | |
switch ( $key ) { | |
case 'google_id': | |
$user_query = new \WP_User_Query( | |
array( | |
'meta_key' => '_google_user_id', | |
'meta_value' => $value | |
) | |
); | |
// Get the results from the query, returning the first user | |
$users = $user_query->get_results(); | |
if ( is_array( $users ) && $users ) { | |
$user = $users[0]; | |
$user = new \WP_User( $user->ID ); | |
} | |
break; | |
default: | |
$user = \get_user_by( $key, $value ); | |
break; | |
} | |
return $user; | |
} | |
/** | |
* Prepare Google Data. | |
*/ | |
protected function prepare_google() { | |
if ( ! $this->googleClient && defined( 'HEADLESS_GOOGLE_CLIENT_ID' ) ) { | |
$this->googleClient = HEADLESS_GOOGLE_CLIENT_ID; | |
} | |
} | |
/** | |
* Create the user. | |
* | |
* @param string $username | |
* @param string $email | |
* | |
* @return integer User ID. | |
*/ | |
protected function create_user( $username, $email, $password = '' ) { | |
if ( username_exists( $username ) ) { | |
$username .= date( 'YmdHis'); | |
} | |
if ( ! $password ) { | |
$password = wp_generate_password(); | |
} | |
$user_id = wp_create_user( $username, $password, $email ); | |
return $user_id; | |
} | |
/** | |
* Create the token for the User. | |
* | |
* @param \WP_User $user User object. | |
* | |
* @return mixed|void|\WP_Error | |
*/ | |
protected function create_token( $user ) { | |
$secret_key = defined('JWT_AUTH_SECRET_KEY') ? JWT_AUTH_SECRET_KEY : false; | |
/** First thing, check the secret key if not exist return a error*/ | |
if (!$secret_key) { | |
return new \WP_Error( | |
'jwt_auth_bad_config', | |
__('JWT is not configurated properly, please contact the admin', 'wp-api-jwt-auth'), | |
array( | |
'status' => 403, | |
) | |
); | |
} | |
/** Valid credentials, the user exists create the according Token */ | |
$issuedAt = time(); | |
$notBefore = apply_filters('jwt_auth_not_before', $issuedAt, $issuedAt); | |
$expire = apply_filters('jwt_auth_expire', $issuedAt + (DAY_IN_SECONDS * 7), $issuedAt); | |
$token = array( | |
'iss' => get_bloginfo('url'), | |
'iat' => $issuedAt, | |
'nbf' => $notBefore, | |
'exp' => $expire, | |
'data' => array( | |
'user' => array( | |
'id' => $user->ID, | |
), | |
), | |
); | |
/** Let the user modify the token data before the sign. */ | |
$token = JWT::encode(apply_filters('jwt_auth_token_before_sign', $token, $user), $secret_key); | |
$data = array( | |
'token' => $token, | |
'user_email' => $user->user_email, | |
'user_nicename' => $user->user_nicename, | |
'user_display_name' => $user->display_name, | |
); | |
/** Let the user modify the data before send it back */ | |
return apply_filters('jwt_auth_token_before_dispatch', $data, $user); | |
} | |
/** | |
* Login with Google. | |
* | |
* @param \WP_REST_Request $request | |
*/ | |
public function login_google( $request ) {} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Headless\REST_API; | |
/** | |
* Class REST_API | |
* | |
* @package Vivant | |
*/ | |
class REST_API { | |
/** | |
* Version of the REST API. | |
*/ | |
protected $version = 'v1'; | |
/** | |
* @var string | |
*/ | |
protected $path = ''; | |
/** | |
* @var array | |
*/ | |
protected $routes = array(); | |
/** | |
* @return array | |
*/ | |
public function get_routes() { | |
return $this->routes; | |
} | |
/** | |
* @param $route | |
* @param $config | |
*/ | |
public function add_route( $route, $config ) { | |
$this->routes[] = array( | |
'route' => $this->version . '/' . $this->path . '/' . $route, | |
'config' => $config | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Handling global REST API requests. | |
*/ | |
namespace Headless; | |
use \Headless\REST_API\Login; | |
/** | |
* Class REST_API | |
* | |
* @package Vivant | |
*/ | |
class REST_APIS { | |
private $routes = array(); | |
public function __construct() { | |
include_once 'rest-api/class-login.php'; | |
} | |
/** | |
* Register all hooks. | |
*/ | |
public function register_hooks() { | |
add_action( 'rest_api_init', array( $this, 'prepare_route' ) ); | |
} | |
/** | |
* Prepare the routes | |
*/ | |
public function prepare_route() { | |
$login = new Login(); | |
$this->routes = array_merge( $this->routes, $login->get_routes() ); | |
foreach ( $this->routes as $route ) { | |
\register_rest_route( 'headless', $route['route'], $route['config'] ); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Plugin Name: Headless WordPress | |
* Description: Plugin to allow certain headless functions | |
*/ | |
namespace Headless; | |
if ( ! defined( 'ABSPATH' ) ) { | |
return; | |
} | |
define( 'HEADLESS_FILE', __FILE__ ); | |
/** | |
* Headless | |
*/ | |
class Headless { | |
public function __construct() { | |
$this->includes(); | |
$this->run(); | |
} | |
public function includes() { | |
include_once 'vendor/autoload.php'; | |
include_once 'includes/abstracts/class-rest-api.php'; | |
include_once 'includes/class-rest-apis.php'; | |
} | |
public function run() { | |
$rest_apis = new REST_APIS(); | |
$rest_apis->register_hooks(); | |
// Used in the previous tutorial on DropZone. | |
if ( function_exists( 'register_meta' ) ) { | |
register_meta( 'user', 'avatar', [ 'show_in_rest' => true ]); | |
register_meta( 'user', 'avatar_id', [ 'show_in_rest' => true ]); | |
} | |
} | |
} | |
new Headless(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { TextControl, Button, Notice } from '@wordpress/components'; | |
import LoginGoogle from './login/LoginGoogle'; //Adding Google Component | |
const axios = require('axios'); | |
class Login extends React.Component { | |
constructor( props ) { | |
super( props ); | |
// ... previous bindings are here. | |
this.setLogin = this.setLogin.bind(this); | |
} | |
setLogin( token ) { | |
localStorage.setItem( 'login', token ); | |
this.props.setLogin( token ); //setLogin is passed in App.js. | |
} | |
render() { | |
return ( | |
<form className="login" method="post"> | |
// Other fields are here. | |
<Button isPrimary onClick={this.handleSubmit}>Login</Button> | |
Or | |
<LoginGoogle url={this.props.url} setLogin={this.setLogin} /> | |
</form> | |
); | |
} | |
} | |
export default Login; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import GoogleLogin from 'react-google-login'; | |
const axios = require('axios'); | |
class LoginGoogle extends React.Component { | |
constructor( props ){ | |
super( props ); | |
this.loginWithGoogle = this.loginWithGoogle.bind(this); | |
} | |
loginWithGoogle( response ) { | |
var self = this; | |
var id_token = response.tokenId; | |
axios.post( this.props.url + '/wp-json/headless/v1/login/google', { token: id_token } ).then( ( resp ) => { | |
self.props.setLogin( resp.data.token ); | |
}); | |
} | |
errorWithGoogle( error ) { | |
console.error( error ); | |
} | |
render() { | |
return ( | |
<GoogleLogin | |
clientId="YOUR_GOOGLE_CLIENT_ID" | |
buttonText="Google" | |
onSuccess={this.loginWithGoogle} | |
onFailure={this.errorWithGoogle} | |
/> | |
) | |
} | |
} | |
export default LoginGoogle; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment