未経験からweb系プログラマーになるための独学履歴~PHP初学者によるOAuth認証チャレンジ編~

##このコーナーについて

 私は根っからの文系なのでプログラミングや情報技術についてはITパスポート程度の知識しかなく、現在は全くの独学でコードや言語等のスキルの勉強はチュートリアル等でコードを書いて成果物を作りながら学んでいけるが、それらの過程で出てくる用語や技術についてはイマイチ学びきれない。
なので、折に触れて書籍及びQlita記事等のネットサーフィンで学んだ座学的知識をアウトプットすることがこのコーナーの目的である。


## OAuth認証にチャレンジ

 [前回](https://qiita.com/shitikakei/items/3c21117484bb9952a6d7) はWeb APIについてその仕組みや使い方を簡単に勉強したので、今度は実践編ということで1番メジャーな使い方であるSNSクライアントによるOA認証、つまりソーシャルログインにチャレンジして見ようという話。
 理由としては、折角PHPのチュートリアルとしてログインフォームその他を作ってきたので就職活動におけるポートフォリオにそれを盛り込みたいというところと、今時分ソーシャルログインのあるWebサービスやWebアプリも珍しいものではないのでこの機会に勉強して実装してみようというところにある。

今回取り上げるクライアントはGoogle、Facebook、Twitterの3つ。
うち、実際にテストし実装を目指すのは前2つである。
本当はTwitterも当然実装したいのだが、後述するがAPIの利用申請を出すのにTwitterアカウントへの電話番号登録が必要であることから断念した。
私用とプログラミング学習用のアカウントは分けてあるので万が一電話番号を登録しなければならない際には私用の方にやらなければならないということと、例えプログラミング用途であっても身バレはあまりしたくないということである。世知辛い。

## 始める前に


 今回作ったcallback.phpとそれに関連する処理（通常ログイン処理）は私が作ったものなので、一応動作はしているがそれがプロから見て正しく、適切なコード処理なのかは正直自信がありません。
 仮に正しいとしてもスマートな方法とは思えないので、あくまでOA認証実装のおまけとして捉えてほしいです。
 もし、エンジニアの方がこの記事をご覧になっていた場合はこの部分に限らずコメント欄で怪しい記述や間違っている部分については忌憚なく指摘していただけるとありがたいです。
特に、通常ログインとOA認証の併用に関してはイマイチ情報を検索できていないので、解説していただけるあるいは、解説している記事等ございましたらコメント欄で紹介していただけると大変助かります。

## 行ったチュートリアル

[Facebook Login With PHP SDK v5 & Graph API Tutorial](https://youtu.be/1DgzTWr3F2I?list=PLgt-G5tEOlKWJ5etlQ0IT-F0DymyiCbon)
[Login With Google Account Using PHP & Client API & Bootstrap Design
](https://youtu.be/hazMyK_cnzk?list=PLgt-G5tEOlKWJ5etlQ0IT-F0DymyiCbon)

## 共通の手順・アルゴリズム

実は手順、及びアルゴリズムとしては3つのクライアントとも似通っていて大まかに言うと

Developerサイトに登録して

・APIの利用の手続きをしてAPIキーやシークレットキーを取得する
・APIを利用するアプリの名前やAPIを利用するリダイレクト先のURLを登録する
・クライアントから得たいデータ取得するための処理をする

というWeb上での手順と

・APIキーや各クライアントのOA認証に用いるライブラリのオートロード、セッションのスタートなどの必要な設定などをまとめ、その後の各ページで読み込むための大本のphpファイルを作る。

・ログインページ、ログイン処理（コールバック・リダイレクト処理のページ）、ログイン成功後に遷移するページの3つのページをPHPで作り、各々処理を書く。
当然、通信状態を保存しなければならないのでSession状態での処理ということになる。

というPHPファイル作成という2つの手順にわかれる。

コード処理の大まかアルゴリズムは以下の通り

1.通常のログインの場合

ログインフォームから通常のログイン形式でログイン
↓
フォームの入力でエラーがあれば、コールバックせずにそのままエラーメッセージを出す。
フォームに入力された値が正しければコールバックし、ログイン後のページへ。
↓
ログアウトボタンが押されるとセッションがすべて破棄され、ログインフォームに戻る

2.ソーシャルログインの場合

ログインフォームからソーシャルログインでログイン
↓
各クライアントのログイン認証画面へリダイレクトされ、認証を行う。
↓
正常に認証されると、アプリ・Webサービス側でのログイン処理(コールバック・リダイレクト処理)が行われ、クライアントから必要な情報やトークン（以下、ユーザー情報とアクセストークン）が発行され、それを元にログイン後の画面へ遷移する。
この時に得たユーザー情報とアクセストークンはセッションに保存される(=ログイン状態が保持される）
↓
ログアウトボタンが押されるとセッションがすべて破棄され、ログインフォームに戻る。

## Googleの場合
使うライブラリ……Google APIs Client Library for PHP

[git](https://github.com/googleapis/google-api-php-client) かComposerからどうぞ。
Composerが扱えるならこちらのが間違いはないでしょう。
使ったことがないという人はググって使い方を探してみてください。
XAMPP環境だと [このサイト](https://4ndan.com/blog/composer/) がわかりやすいです。

Developerは[こちら](https://console.developers.google.com/?hl=JA)。


```PHP:confighttps://console.developers.google.com/?hl=JA.php

<?php 
session_start();
require_once "Google API/vendor/autoload.php";
gClient->setClientId(); //APIキーの値
$gClient->setClientSecret(); //APIシークレットキーの値
$gClient->setApplicationName(); //APIを利用するアプリの名前
$gClient->setRedirectUri(); //リダイレクト先のURLの指定
$gClient->addScope(); //Googleから得たい情報にアクセスするための文字列 ?>

```

　設定ファイルです。
　関数が書いてあるファイル以外はほぼこのファイルを読み込むことから始まるのということと、ログイン処理ということSESSIONを使わなければいけないので必ず冒頭のsession_startを忘れないようにしましょう。
　4~8行目の部分はDeveloperのサイトで各々設定したり、取得したものを引数に指定してあげてください。

```php:login.php

<?php 
	require_once "config.php";

	$loginURL= $gClient->createAuthUrl();

	$msg = "";

	if (isset($_SESSION['email']) && isset($_SESSION['password'])) {

	header("location:index.php");
	exit();

	}

// すでに通常のログイン方式でログインしている場合リダイレクトする。

	if(isset($_POST['submit'])) {

	$conn = new mysqli("localhost","root","","register");

	$email = $conn->real_escape_string($_POST['email']);
	$password = $conn->real_escape_string($_POST['password']);

	$_SESSION['email'] = $email;
	$_SESSION['password'] = $password;


	if ($email == "" || $password == "")
		$msg = "Please check your inputs!";
	else {
		$sql = $conn->query("SELECT id, password, isEmailConfirmed FROM users WHERE email = '$email' ");
		
		if ($sql->num_rows > 0) {

			$data = $sql->fetch_array();
			
			if(password_verify($password,$data['password'])) {

				if($data['isEmailConfirmed'] == 0)
					$msg= "Please verify your Email!";

				else {

					header("location: callback.php");
					exit();


				}

			} else
				$msg = "Please check your Password!";
			
		} else {
			$msg = "Please check your Email!";

			}
		}
	}

// 通常のログイン方式でログインする一連の処理
 ?>


<!DOCTYPE html>
<html>
<head>
	<title>Login With Google API</title>
	<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
	<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
	<link rel="stylesheet" type="text/css" href="css/login.css">
</head>
<body>
<div class="container">
	<div class="row justify-content-center">
		<div class="col-md-6 md-offset-3 form">

				<?php 
				if ($msg != "")
					echo $msg . "<br><br>";

				 ?>

			<form action="login.php" method="POST">
				
				<input class="form-control" type="text" name="email" placeholder="Email......"><br>
				<input class="form-control" type="password" name="password" placeholder="Password......"><br>
				<input class="btn btn-primary" type="submit" name="submit" value="Log In" >
				<input class="btn btn-danger" type="button" 
				onclick="window.location = '<?php echo $loginURL; ?>'" value="Log In with Google" >

//ソーシャルログインでログインする

			</form>
			
		</div>
	</div>
</div>
</body>
</html>


```
 ログインフォームにあたるコードです。
 ポイントは通常のログイン方式とソーシャルログインを併用させていること。
 私はPHP超初学者なのでおそらくこの方法はかなりスマートではなく力技でやっているような気がしてならないのですが、一応はどちらでログインしても問題無い状態です。
 私が調べた限りであれば本来であれば、フレームワークを用いるかそうでなくてもテーブルを2つ用意してそれを関連付けて……といったことをしなければならないのですが、今回はあくまでOA認証を実装するということが主目的なのと、単純に併用はどうやったら可能なのかということを今自分が持ってるもので探って行きたかったのでこういった形になりました。本番はもちろんLaravelを使ってということになりますが、その前の種まきだと思ってください。ただ、いまいちググってもフレームワーク使えば楽だよみたいなことしか書いていなくて不安が増す一方なので、通常のログイン方式とソーシャルログインを並列させるようなテーブルの作り方とアルゴリズムについてアドバイス頂ければととても嬉しいです。
ソーシャルログインのボタンの部分でonclickを使っているが、本来はHTML要素に直接書き込むのはあまり望ましいものではないみたいなので本実装の際はscriptタグで書くなどHTMLとは極力分離する。

```PHP:callback系

// callback.php

<?php
require_once "function.php";

session_start();

	if(isset($_SESSION['email']) && isset($_SESSION['password'])) {
		$LoginToken = generateNewString(); //トークン生成
		$_SESSION['LoginToken'] = (string) $LoginToken; //セッションにトークンを保存
		header("location: index.php");//ログイン後のページへ遷移
		exit();

	}



 ?>

// 通常ログインで用いるコールバック




// g-callback.php
<?php 
	require_once "config.php";
	

	if (isset($_SESSION['access_token'])) {
		$gClient->setAccessToken($_SESSION['access_token']);
	
	} else if (isset($_GET['code'])) {
		$token = $gClient->fetchAccessTokenWithAuthCode($_GET['code']);
		$_SESSION['access_token'] = $token;
	
	} else {

		header("location: login.php");
		exit();


	}

	

	$oAuth = new Google_Service_Oauth2($gClient);
	$userData = $oAuth->userinfo_v2_me->get();

	$_SESSION['id'] = $userData['id'];
	$_SESSION['email'] = $userData['email'];
	$_SESSION['gender'] = $userData['gender'];
	$_SESSION['picture'] = $userData['picture'];
	$_SESSION['familyname'] = $userData['familyname'];
	$_SESSION['givenName'] = $userData['givenName'];

	header("location: index.php");
	exit();

 ?>

// ソーシャルログインで用いるコールバック

```
 後者がライブラリを用いて書いたもので前者は私が書いたもの。
 メソッドやクラスなどはライブラリで用意されているものを使えばいいので、ググればテンプレートはいくらでも見つかる。
 何をやっているのかということは押さえておかないといけないので解説をつけてみたのが以下である。

```php:解説あり


// g-callback.phpについて

if (isset($_GET['code'])) {
		$token = $gClient->fetchAccessTokenWithAuthCode($_GET['code']);
		$_SESSION['access_token'] = $token;
	
	}

// トークンの取得

	$oAuth = new Google_Service_Oauth2($gClient);
// インスタンス化。autoloadの中のクラスのうちGoogle_Service_Oauth2を引数を$gClientにしてインスタンス化する。
	$userData = $oAuth->userinfo_v2_me->get();
// ユーザーデータを引き出す。Google_Service_Oauth2のuserinfo_v2_meプロパティにアクセスしてgetメソッドで取得。

	echo "<pre>";
	var_dump($userData);
// 取得したデータを確認するためにvar_dumpで書き出してみる。

	$_SESSION['id'] = $userData['id'];
	$_SESSION['email'] = $userData['email'];
	$_SESSION['gender'] = $userData['gender'];
	$_SESSION['picture'] = $userData['picture'];
	$_SESSION['familyname'] = $userData['familyname'];
	$_SESSION['givenName'] = $userData['givenName'];

//先程書き出した連想配列から取得したいデータを選んで$_SESSIONに代入する

	header("location:index.php");
	exit();

// トークンと取得したいデータが揃ったらログイン後に表示するページへリダイレクトする。
// 重ねてになるが処理順はセッションにトークンが保存されているかの確認→保存されていないならトークン取得、できなかったらログインフォームにリダイレクト→ユーザーデータの取得となる。


 ?>

```
セッション変数にデータを代入するという考え方は非常に大切なので覚えておこう。
大雑把に行ってしまえば、セッション変数にデータが保存されているという状態が、すなわちログイン状態を保っているということであると言っていい。なぜならセッション状態というのはページをまたいでもそのまま保持されているからだ。
イマイチSESSIONについてピンときてなかった私もこの処理をみて漸く少しはその役割について理解できたような気がして、callback.phpで拙いながらも応用している。

```php:function.php

<?php 
	function generateNewString($len = 10) {
		$token = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789!$/()*';
		$token = str_shuffle($token);
		$token = substr($token, 0, $len);

		return $Ltoken;

	}

	function redirectToLoginPage() {
	header('location:login.php');
 	exit();
}

 ?>



```

関数を抜き出しておいてあるページ、1つ目の関数はトークンを生成する簡単な関数。
2つ目はログインフォームへのリダイレクト処理を行う関数。
こうしておけばrequire_onceで読み込んで関数名を宣言してやるだけで使える。

```php:index.php


<?php 
	session_start();

	if(!isset($_SESSION['access_token'])&&!isset($_SESSION['LoginToken'])) {
		header("location: login.php");
		exit();
	}

 ?>


<!DOCTYPE html>
<html>
<head>
	<title>Login With Google API</title>
	<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
	<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
	<link rel="stylesheet" type="text/css" href="css/index.css">
</head>
<body>
<div class="container">
	<div class="row  content">
		<div class="col-md-3 icon">
			<img src="<?php echo $_SESSION['picture'] ?>">
			
		</div>

		<div class="col-md-9">
			<table class="table table-hover table-bordered">
				<tbody>
					<tr>
						<td>ID</td>
						<td><?php echo $_SESSION['id']; ?></td>
					</tr>

					<tr>
						<td>First Name</td>
						<td><?php echo $_SESSION['givenName']; ?></td>
					</tr>

					<tr>
						<td>Last Name</td>
						<td><?php echo $_SESSION['familyname']; ?></td>
					</tr>

					<tr>
						<td>Email</td>
						<td><?php echo $_SESSION['email']; ?></td>
					</tr>

					<tr>
						<td>Gender</td>
						<td><?php echo $_SESSION['gender']; ?></td>
					</tr>
				</tbody>
			</table>

			<input class="btn btn-danger" type="button" 
				onclick="window.location = 'logout.php'" value="Log Out" >
			
		</div>
	</div>
</div>
</body>
</html>


```
ログイン後のページ、今回はソーシャルログインがメインなのでソーシャルログインにおいてSNSクライアントから引張り出してきたデータをtableで出力している。
が、これは実にスマートではないうえ、php echoの部分はhtmlspecialcharsでエスケープするのが望ましいそうな。

```php:logout.php

<?php 
	require_once "config.php";
	unset($_SESSION['access_token']);
	$gClient->revokeToken();
	//セッション変数を全て解除
	$_SESSION = array();
  
	//セッションクッキーの削除
	if (isset($_COOKIE["PHPSESSID"])) {
    setcookie("PHPSESSID", '', time() - 1800, '/');
	}
	session_destroy();
	header("location:login.php");
	exit();

 ?>

```

ログアウトのためのファイル。
Sessionに保存したもの及びCookieをすべて破棄する処理である。
これをやらないとログイン情報などがいつまでも残ったままになってしまい、リスクが高まる。


## Facebookの場合

使うライブラリ......Facebook SDK for PHP
こちらも[公式のガイド](https://developers.facebook.com/docs/php/gettingstarted) からGitへ飛んでDLするかComposerからどうぞ
FacebookのOA認証はGoogleに比べてわかりにくいのでComposerでやったほうが間違いがないと思います。

Developerは[こちら](https://developers.facebook.com/?locale=ja_JP)。

APIの利用申請及び許可にはGoogleの場合はGoogleアカウントだけで問題ないが、FacebookとTwitterの場合は電話番号認証が必須であることに注意。
乱用の防止なのかもしれないが非常に面倒な上に、アカウントバレや万が一アカウントロックが掛かった場合はDeveloperにアクセスすることもできなくなるので非常に不便。
実際に本番開発で使う分には理解できるのだが、私のような初学者が勉強のために利用するにはとてもハードルが高く、気軽に利用できないというところはいただけないと思う。
事実、このために作っただけのFacebookアカウントはこの記事を書くために使うスクリーンショットを撮ろうと思ってアクセスしたら、BOTなどと間違われたのかロックがかかっていた。
TwitterもDeveloperに登録しようとアクセスしたらロックをかけられたので、この仕様には非常に疑問を覚える。

閑話休題。

後の手順は、Developerにアクセスしたらアカウントにログインをし、新規アプリ開発から進んでいって、Display Name、AppDomains（今回はlocalhost)など必要な項目を記載して、プラットフォームを選択し、URLのところにrootとなるURLをいれる。
その後クライアントOAuth設定でcallback.phpをリダイレクトURIに設定する。

API Explorer（グラフAPIエクスプローラー）でクライアントから得る情報の許可アクセストークンを発行する。
今回必要ならuser_birthdayとemailなので、idと名前とメールアドレスへのアクセス許可トークンを発行する。
スクリーンショットを本来なら貼るべきなのだが、前述の通りロックをかけられてしまったので割愛。参考資料に解説記事を載せておくのでご容赦いただきたい。

以下コード

```PHP:config.php

<?php 
	
	session_start();
	date_default_timezone_set('Asia/Tokyo'); // タイムゾーン設定、念の為に書いておく。

	require_once "Facebook/autoload.php";

	

	 $FB = new \Facebook\Facebook([
	 	'app_id' => '',
	 	'app_secret' => '',
	 	'default_graph_version' => ''
	 ]);	

        $helper = $FB->getRedirectLoginHelper();


 ?>
```

　設定ファイルです。
　Googleの時とやることは同じですがプロパティへのアクセスの書き方が違うところと「default_graph_version」のところをグラフAPIエクスプローラーで表示されているバーションにするというところは注意。

```php:login.php

<?php

	require_once "config.php";

	$msg = "";


	if (isset($_SESSION['access_token'])) {
		header('Location: index.php');
		exit();
	}

	$redirectURL = "http://localhost/Laravel/PHPMailer/Training/OAFacebook/fb-callback.php";
	$permissions = ['email'];
	$loginURL = $helper->getLoginUrl($redirectURL, $permissions);


	if (isset($_SESSION['email']) && isset($_SESSION['password'])) {

	header("location:index.php");
	exit();

	}

	if(isset($_POST['submit'])) {

	$conn = new mysqli("localhost","root","","register");

	$email = $conn->real_escape_string($_POST['email']);
	$password = $conn->real_escape_string($_POST['password']);

	$_SESSION['email'] = $email;
	$_SESSION['password'] = $password;


	if ($email == "" || $password == "")
		$msg = "Please check your inputs!";
	else {
		$sql = $conn->query("SELECT id, password, isEmailConfirmed FROM users WHERE email = '$email' ");
		
		if ($sql->num_rows > 0) {

			$data = $sql->fetch_array();
			
			if(password_verify($password,$data['password'])) {

				if($data['isEmailConfirmed'] == 0)
					$msg= "Please verify your Email!";

				else {

					header("location: callback.php");
					exit();


				}

			} else
				$msg = "Please check your Password!";
			
		} else {
			$msg = "Please check your Email!";

			}
		}
	}



?>
<!DOCTYPE html>
<html>
<head>
	<title>Login With Google API</title>
	<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
	<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
	<link rel="stylesheet" type="text/css" href="css/login.css">
</head>
<body>
<div class="container">
	<div class="row justify-content-center">
		<div class="col-md-6 md-offset-3 form">

				<?php 
				if ($msg != "")
					echo $msg . "<br><br>";

				 ?>

			<form action="login.php" method="POST">
				
				<input class="form-control" type="text" name="email" placeholder="Email......"><br>
				<input class="form-control" type="password" name="password" placeholder="Password......"><br>
				<input class="btn btn-primary" type="submit" name="submit" value="Log In" >
					<input type="button" onclick="window.location = '<?php echo $loginURL; ?>'" value="Log In With Facebook" class="btn btn-primary">
				</form>
			</div>
		</div>
	</div>

</body>
</html>
?>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Log In</title>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
</head>
<body>

	<div class="container" style="margin-top: 100px">
		<div class="row justify-content-center">
			<div class="col-md-6 col-md-offset-3" align="center">
				<img src="images/logo.png"><br><br>
				<form>
					<input name="email" placeholder="Email" class="form-control"><br>
					<input name="password" type="password" placeholder="Password" class="form-control"><br>
					<input type="submit" value="Log In" class="btn btn-primary">
					<input type="button" onclick="window.location = '<?php echo $loginURL; ?>'" value="Log In With Facebook" class="btn btn-primary">
				</form>
			</div>
		</div>
	</div>

</body>
</html>
```
 ログインフォームにあたるコード。通常ログインの部分はGoogleのものから流用しています。
 Googleのものと比べて違うところは以下の部分。

```php:
    $redirectURL = "http://localhost/Laravel/PHPMailer/Training/OAFacebook/fb-callback.php";
	$permissions = ['email'];
	$loginURL = $helper->getLoginUrl($redirectURL, $permissions);

```
$loginURL（つまり、コールバック処理及びその前のSNSクライアントへのログインへ遷移するためのURLの値）に$permissionsがあること。
これは指定した値によってユーザーがログイン認証に関して何を許可するかということが変わるもので、例えば「email」のみの場合は自分の「公開プロフィールとメールアドレスという情報をアプリ側に渡すことを許可してもいいか？」 ということをユーザーは許可するかしないのか選択するということになる。
許可した場合はSNSクライアント側のログイン認証→コールバック→アプリ側のログイン認証といった具合に処理が進んでいく。


```PHP:callback系

// callback.php

<?php
require_once "function.php";

session_start();

	if(isset($_SESSION['email']) && isset($_SESSION['password'])) {
		$LoginToken = generateNewString(); //トークン生成
		$_SESSION['LoginToken'] = (string) $LoginToken; //セッションにトークンを保存
		header("location: index.php");//ログイン後のページへ遷移
		exit();

	}



 ?>

// 通常ログインで用いるコールバック




// fb-callback.php
<?php 
	require_once "config.php";

	try {
		$accessToken = $helper->getAccessToken();
	} catch (\Facebook\Exceptions\FacebookResponseException $e) {
		echo "Response Exception: " . $e->getMessage();
		exit();
	} catch (\Facebook\Exceptions\FacebookSDKException $e) {
		echo "SDK Exception: " . $e->getMessage();
		exit();
	}

	if (!$accessToken) {
		header('Location: login.php');
		exit();
	}

	$oAuth2Client = $FB->getOAuth2Client();
	if (!$accessToken->isLongLived()) {
		$accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
	}

	$response = $FB->get("/me?fields=id, first_name, last_name, email, picture.type(large)", $accessToken);
	$userData = $response->getGraphNode()->asArray();
	$_SESSION['userData'] = $userData;
	$_SESSION['access_token'] = (string) $accessToken;
	//セッション中の$userData及び、アクセストークンをセッション内に保存する。
	header("location:index.php");
	exit();

	
 ?>
// ソーシャルログインで用いるコールバック

```
前者は引き続きGoogleのものから流用。
後者に関しては以下のような手順となっている。

```php:
try {
		$accessToken = $helper->getAccessToken();
	} catch (\Facebook\Exceptions\FacebookResponseException $e) {
		echo "Response Exception: " . $e->getMessage();
		exit();
	} catch (\Facebook\Exceptions\FacebookSDKException $e) {
		echo "SDK Exception: " . $e->getMessage();
		exit();
	}

	if (!$accessToken) {
		header('Location: login.php');
		exit();
	}


```
まずは、try-catch構文でアクセストークンにアクセスする。
できなかったら例外メッセージを出力してプログラムを終了するレスポンス部分で例外が起きたときはSDK EXceptionsに例外を投げるという処理。
いずれも上手くいかず、アクセストークンが取得できなかった場合はログインフォームへリダイレクト。

```php:
$oAuth2Client = $FB->getOAuth2Client();
	if (!$accessToken->isLongLived()) {
		$accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
	}

```
トークンの有効期限の設定を行う。

```php:
$response = $FB->get("/me?fields=id, first_name, last_name, email, picture.type(large)", $accessToken);

```
セッション中の$userData及び、アクセストークンをセッション内に保存する。
クライアントから情報を得るために、まずインスタンスである＄FBに引数にアクセストークン（要は書類庫の鍵みたいなもの）を設定し、getでアクセスして必要な情報を取得し変数に格納する。
それを単一のオブジェクトとしてアクセスし、さらにそれを連想配列の形でアクセス。それを別の変数に格納する。

```php:

$_SESSION['userData'] = $userData;
$_SESSION['access_token'] = (string) $accessToken;

```
取得したデータ及びトークンをセッション内に保存する。

```php:

header("location:index.php");
exit();

```

ログイン後のページへリダイレクトする。





```php:function.php

<?php 
	function generateNewString($len = 10) {
		$token = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789!$/()*';
		$token = str_shuffle($token);
		$token = substr($token, 0, $len);

		return $:Ltoken;

	}

	function redirectToLoginPage() {
	header('location:login.php');
 	exit();
}

 ?>



```
Googleのときに使ったものを流用。

```php:index.php


<?php
	session_start();

		if(!isset($_SESSION['access_token'])&&!isset($_SESSION['LoginToken'])) {
		header("location: login.php");
		exit();
	}

 ?>

 ?>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>User Profile</title>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
	<link rel="stylesheet" type="text/css" href="css/index.css">
</head>
<body>
	<div class="container">
		<div class="row justify-content-center">
			<div class="col-md-3">
<!-- 				<img src="<?php echo htmlspecialchars($_SESSION['userData']['picture']['url']) ?>"> -->
			</div>

			<div class="col-md-9">
				<table class="table table-hover table-bordered">
					<tbody>
						<tr>
							<td>ID</td>
							<td><?php echo htmlspecialchars($_SESSION['userData']['id']) ?></td>
						</tr>
						<tr>
							<td>First Name</td>
							<td><?php echo htmlspecialchars($_SESSION['userData']['first_name']) ?></td>
						</tr>
						<tr>
							<td>Last Name</td>
							<td><?php echo htmlspecialchars($_SESSION['userData']['last_name']) ?></td>
						</tr>
						<tr>
							<td>Email</td>
							<td><?php echo htmlspecialchars($_SESSION['userData']['email']) ?></td>
						</tr>
					</tbody>
				</table>
			</div>
			<input type="button" onclick="window.location = 'logout.php'" value="Log Out With Facebook" class="btn btn-danger">
		</div>
	</div>
</body>
</html>


```
ログイン後のページ、Googleの時のものとほぼ変わらない。
今回は、php echoの部分はhtmlspecialcharsでエスケープしている。

```php:logout.php

<?php
session_start();
  
header("Content-type: text/html; charset=utf-8");
 
//セッション変数を全て解除
$_SESSION = array();
  
//セッションクッキーの削除
if (isset($_COOKIE["PHPSESSID"])) {
    setcookie("PHPSESSID", '', time() - 1800, '/');
}
  
//セッションを破棄する
session_destroy();

header("location:login.php");
	exit();
```
Googleのものから流用。



## Twitterの場合

使うライブラリ......twitteroauth
[Git](https://github.com/abraham/twitteroauth) or Composerから導入。
Developerは[こちら](https://developer.twitter.com/content/developer-twitter/ja.html) 。

Facebookと同じく利用には電話番号認証が必要なのだが、Twitterの場合はさらに厳格で利用目的などを英語で記載して利用の申請し、その上で利用許諾が下りるのを待たなければならないので気軽に導入することができないことに注意。
以下、参考記事

[Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ](https://qiita.com/kngsym2018/items/2524d21455aac111cdee)

私はそもそも電話番号認証をすることができない(私用のアカウントとプログラミングアウトプット用のアカウントと分けて使っている)ので、今回はコード上のみでの学習となる。
なので、コピペはNG。

以下コード。今回はテストをしていないのでTwitterのOA認証に関係する部分のみ取り上げていく。

```PHP:config.php

<?php
	require_once "twitteroauth/vendor/autoload.php";

	use Abraham\TwitterOAuth\TwitterOAuth;

	define('CONSUMER_KEY','');

	define('CONSUMER_SECRET','');
	
	define('OAUTH_CALLBACK',''); // コールバックページのURLを指定

 ?>
```

　おなじみの設定ファイルです。
　autoloadの読み込みとuse宣言を忘れないようにするのと、例によってDeveloper側で登録したコールバックURLをここでも登録しておく。

```php:login.php

<?php
	session_start();

	require_once "config.php";

	$connection = new TwitterOAuth(CONSUMER_KEY,CONSUMER_SECRET);

	$request_token = $connection->oauth('oauth/request_token',
		array('oauth_callback' => OAUTH_CALLBACK)
		);

	$_SESSION['oauth_token'] = $request_token['oauth_token'];
	$_SESSION['oauth_token_secret'] = $request_token['oauth_token_secret'];

	$loginURL = $connection->url('oauth/authenticate', array('oauth_token' => $request_token['oauth_token'])
		);

 ?>

 <!DOCTYPE html>
 <html>
 <head>
 	<title>Twitter API Login Tutorial</title>
 	<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
	<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
	<link rel="stylesheet" type="text/css" href="css/login.css">
 </head>
 <body>
 	<div class="container">
 		<div class="row justify-content-center form">
 			<div class="col-md-6 md-offset-3">
 				<input class="form-control" type="text" name="email" placeholder="Email......">
 				<input class="form-control" type="password" name="password" placeholder="Password......">
 				<input class="btn btn-primary" type="submit" value="Login">
 				<input class="btn btn-primary" onclick="window.location = '<?php echo $loginURL; ?>'" type="button" value="Log In with Twitter" >
 			</div>
 		</div>
 	</div>
 
 </body>
 </html>
```
ログインフォームにあたるコード。
細かく見ていくと以下の通り。

```php:

// インスタンス化、configで設定したAPIキーとシークレットキーを引数に設定する。
$connection = new TwitterOAuth(CONSUMER_KEY,CONSUMER_SECRET);


// トークンのリクエストをする、configで設定したコールバックURLを設定する。

	$request_token = $connection->oauth('oauth/request_token',
		array('oauth_callback' => OAUTH_CALLBACK)
		);


// リクエストしたトークンはコールバックでも使うので値をセッションに保存する。
	$_SESSION['oauth_token'] = $request_token['oauth_token'];
	$_SESSION['oauth_token_secret'] = $request_token['oauth_token_secret'];


// Twitter認証画面へのURL
	$loginURL = $connection->url('oauth/authenticate', array('oauth_token' => $request_token['oauth_token'])
		);



```

インスタンス化→トークンリクエスト→リクエストしたトークンをセッションに保存→Twitterの認証画面へという流れを理解する。




```PHP:callback系

//  fb-callback.php

use Abraham\TwitterOAuth\TwitterOAuth;

// login.phpで保存したセッションをもう一度記載。

	$request_token = []; // [] = array() 
	$request_token['oauth_token'] = $_SESSION['oauth_token'];
	$request_token['oauth_token_secret'] = $_SESSION['oauth_token_secret'];


// Twitterから返されたOAuthトークンと、先に保存したセッションの値とを一致するか確かめる。一致しない場合は、エラーメッセージ。

	if(isset($_REQUEST['oauth_token']) && $request_token['oauth_token'] !==
		$_REQUEST['oauth_token']
		) {

		exit("ERROR!");
	}

// インスタンス化。login.phpと違うのは取得したOAuthトークンも今回は用いる。

	$connection = new TwiiterOAuth(CONSUMER_KEY,CONSUMER_SECRET,$request_token['oauth_token'],$request_token['oauth_token_secret']);

// アクセストークンを定義してセッションに保存する。oauth_verifierをキーに$_REQUEST['oauth_verifier']を値にした連想配列にしたものが$_SESSION['access_token'] に代入される。中身はOAuthトークンやトークンシークレットとなる。

$_SESSION['access_token'] = $connection->oauth("oauth/access_token", array("oauth_verifier" => $_REQUEST['oauth_verifier']));


// 現在のセッションIDを新しく生成したものと置き換え、index.phpへリダイレクト。

	session_regenerate_id();
	header('location: index.php');


 ?>
```

use宣言を忘れないこと。
$_REQUEST変数は$_COOKIE・$_POST・$_GETを統合したものであり、その場に応じて優先順位に従いそれらの処理を行う変数。
通常はCOOKIE ＞ POST ＞ GETの順で優先される。
つまり


```php:

oauth('oauth/access_token', array('oauth_verifier' => $_GET['oauth_verifier'], 'oauth_token'=> $_GET['oauth_token']));



```

と書くのと同じということである。



```php:index.php


<?php

session_start();

require_once 'config.php';
require_once 'twitteroauth/autoload.php';

use Abraham\TwitterOAuth\TwitterOAuth;

// コールバックでセッションに保存しておいたアクセストークンを呼び出す。
$access_token = $_SESSION['access_token'];


// インスタンス化、前2つから更に用意されたOAuthトークン及びトークンシークレットを引数にする。
$connection = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, $access_token['oauth_token'], $access_token['oauth_token_secret']);

// ユーザー情報をGETメソッドで取得
$user = $connection->get("account/verify_credentials");

// 取得したユーザー情報をvar_dumpする。（本番では削除）
var_dump( $user );
?>

```
ログイン後のページ、HTML部分は任意のものを用意してください。
ここまで見ると

```php
// config.php（各ページで使われる値の集合体)
//↓
login.php
//入手済みの値
CONSUMER_KEY,CONSUMER_SECRET
//次に必要になるので定義する値
$_SESSION['oauth_token'],$_SESSION['oauth_token_secret']
//↓
callback.php
//入手済みの値
CONSUMER_KEY,CONSUMER_SECRET
$_SESSION,['oauth_token'],$_SESSION['oauth_token_secret']
//↓
//次に必要になるので定義する値
$_SESSION['access_token'] 
//↓
index.php
//最終的に必要になった値
CONSUMER_KEY, CONSUMER_SECRET,
$access_token['oauth_token'], $access_token['oauth_token_secret']

```

と段階を踏んでトークンやシークレットを定義、入手しながらのログインとなっていることをコードを見て理解できるようになっていることがこの学習の肝である。

logout.phpは割愛。



## あとがき

ソーシャルログインにおいて、導入しなければならないであろう3つのSNSクライアントによるOAuth認証をPHPで導入するにはということで実際にやってみた（Twitterではテストはできてないが）。
ここから通常ログインや複数のソーシャルログインを併用させるとなると複数のテーブルの作成及び紐付けなどなど、複雑で煩雑な仕事が待っている。
そのあたりを上手いことやってくれるのがフレームワークということで、次はPHPでのクラスなどを改めて勉強しつつ、Laravelの学習へ入っていこうと思っている。


## 参考

[Login With Google Account Using PHP & Client API & Bootstrap Design](https://www.youtube.com/watch?v=hazMyK_cnzk&list=PLgt-G5tEOlKWJ5etlQ0IT-F0DymyiCbon&index=3&t=0s)

[今更ながらPHPでFacebook API でログイン処理を書いてみた](http://c-geru.com/as_blind_side/2017/04/facebookapiphp.html)

[【2019年】これで完璧！Facebook Developerに登録する方法を図解つきで説明](https://pasolack.com/sns/facebook-developer-entry/)

[phpでTwitterログイン機能を実装[twitteroauth]](https://4ndan.com/blog/twitter_login/)

[【PHP】新TwitterOAuthでログイン機能を実装する](https://qiita.com/sofpyon/items/982fe3a9ccebd8702867)

[session_regenerate_id
](https://www.php.net/manual/ja/function.session-regenerate-id.php)

[PHP $_REQUEST（リクエスト変数）のすべて！【初心者向け基本】](https://wepicks.net/phpref-request/)