バックエンド初心者がPHPとMySQLでID&パスワード認証を実装してみた


こんにちは。とてつもなく暑い日々が続いていますね。僕は実家が山中にあるもので、帰省自粛の空気の中少しでも涼しい場所を求めてと帰省していましたが、山中ですら30℃を優に超える暑さでクーラーが手放せませんでした。クーラーつけるんなら帰省してもしなくても一緒だったな…

さて、記事の初投稿からなんと2ヶ月が経っていました。最初の記事であんなに偉そうにブログのメリットについて言っておきながら、自身が全くそれを体現できておらず、恥ずかしい限りです。一応の言い訳としては、講義のオンライン化による弊害で課題の量が例年に比べてえげつないほど多く、押し潰されそうになりながら格闘しておりました。入学前までは、「大学院の講義は楽単」なんて話を聞いていましたが、オンライン化という思わぬ例外によって完全に塗り替えられましたね。

さあ、そんな言い訳も夏休みに入ったのでもう通用しません。ということで随分とブランクがありましたが、2本目の記事を書こうと思います。

バックエンド初心者がPHPMySQLを触ってみた

タイトルにもあるとおり、私いとゆう、これまでフロントエンドは多少いじってきたものの、バックエンドは全くの未経験です。バックエンドエンジニアのインターンを勢いで申し込んでみましたが、秒でお祈りメールが返ってきました。今どきは新卒採用でも技術や経験が求められる時代、世知辛い世の中です。

しかし嘆いてばかりはいられません。山のような課題から解放された今こそ、新しい技術を身に付けるとき…!ということで、この夏はバックエンドの技術を使ったWebサービスを作ってみることにしました。

サービスの詳細は形になった頃にでもまた記事にします(多分)。

バックエンド系の言語は色々あり何を使うか迷いましたが、Node.jsやpythonでは新鮮味がないかと思い今回はPHPを選択しました。そして、データベースはPHPと相性がいいとのことでMySQLで管理することにしました。

作りたいものの仕様(ざっくり)

ひとまずは、今回作りたいものの仕様をざっくりとまとめておきます。

<ルーム作成画面>

・8文字以上16文字以内の半角英数字をパスワードとして入力し、ルーム作成ボタンを押すことで固有のIDが割り振られたルーム(phpファイル)をサーバ上に作成する

・ルームIDはハッシュ化されたパスワードとともにデータベースに保存される

・ルーム作成完了画面には、このページを経由することでしかアクセスできない。

(正確には、アクセスしても強制的にルーム作成画面に飛ばされる)

CSRFクロスサイトリクエストフォージェリ)対策

<ルーム作成完了画面>

・ルーム作成画面から正式な手続きを踏んでルームを作成した場合にのみ移動する。

・ルーム作成完了の通知とともに、自動的に割り当てられたルームIDが表示され、ルーム入室ボタンを押すことでルーム入室画面へと移動する。

<ルーム入室画面>

・ルームIDとパスワードを入力し、ルーム入室ボタンを押すことで、IDが有効かつパスワードが正しい場合にのみルームのアドレスに飛ぶ。

・ルームには、このページを経由することでしかアクセスできない。

(正確には、アクセスしても強制的にルーム入室画面に飛ばされる)

CSRFクロスサイトリクエストフォージェリ)対策

<ルーム画面>

・ルームIDだけを表示するシンプルな画面。この部分を色々と作り替えていく予定。

いざ、実装

今回、初のPHPを用いたWebページの実装ということで、まずは効率的な開発ができる環境づくりから始めました。PHPはサーバサイドで動く言語なので、まずはサーバを立てない事には作成したファイルの動作を確認しようがありません。そこで、XAMPPというツールを用いてローカルサーバを立ち上げました。

↓こちらの記事を参考にしました。

超簡単!PHPプログラムをローカルで動作確認するための環境構築方法 | HPcode

XAMPPのモジュールにはMySQLも含まれているのでPHPの動作環境とともにデータベース構築のための環境も整えることができちゃいます。ちなみにデータベースの場合、ルートディレクトリは~\xampp\mysql\binになります。

ルーム作成画面(makeroom.php

f:id:Ito-you:20200822024946p:plain

ルーム作成画面

 

        <form autocomplete='off' method='post' action='makeroom_complete.php' 
onsubmit="return checkForm()">
            <div>
                <input class='password-form-for-makeroom' id='password-form-for-makeroom' type='text' 
name='password'>
                <input type="hidden" name="csrf_token" value="<?php echo $token?>">
                <input class='submit-button' type='submit' value='ルームを作成'>
            </div>
        </form>
ソースコード(一部抜粋)

 

テキストボックスとボタンの部分は<form>タグで囲んでいます。formタグのaction属性にデータを送信したいファイルのパスを設定することで、ボタンをクリックした際にそのファイルにデータを渡すとともに、遷移することができるんですね。

渡すデータは<input>タグのvalue属性に格納しておくようです。このinputタグ、変幻自在という感じで、type属性をtextにすることでテキストボックスに、submitにすることで送信ボタンになります。更にhiddenにするとユーザからは見えなくなります。hiddenはユーザーから見える必要のないものの送信に使われるようです。今回はページを開くと同時にCSRF対策のワンタイムトークンを生成し、これの送信に使用しています。

また、抜粋したソースコードには含まれていませんが、ワンタイムトークンはphp連想配列$_SESSIONにも格納しておきます。$_SESSIONに格納されたデータはサーバに保存されるため、サーバ上であればファイルをまたいでも参照することができます。

↓参考にした記事はこちら

PHPでセッションを使う方法【初心者向け】 | TechAcademyマガジン

ルーム作成完了画面(makeroom_complete.php

f:id:Ito-you:20200822232027p:plain

ルーム作成完了画面
/* CSRF対策 */
if($_SESSION['csrf_token']!=$_POST['csrf_token']){
    $_SESSION['error_status']=1;
    redirect_to_makeroom();
    exit();
}

$link=mysqli_connect('localhost',USER_NAME, PASSWORD, TABLE);
$password=$_POST['password'];

$roomid;
for ($i=10000;$i<=20000;$i++){
    $result=mysqli_fetch_array(mysqli_query($link,'SELECT * FROM room WHERE id='.$i.' LIMIT 1;'));
    if(empty($result)){
        $roomid=$i;
        break;
    }
}
$hash=password_hash($password, PASSWORD_DEFAULT);
$result2=mysqli_query($link,"INSERT INTO room(id,passwordVALUES(".$roomid.",'".$hash."');");

mysqli_close($link);
ソースコード(一部抜粋)

 

画面の構成はシンプルなのでルーム作成手続きの部分だけ抜粋しました。

まずはCSRF対策の部分から見ていきます。この部分の目的は、ルーム作成画面からのpost処理を介したアクセス以外を受け付けないようにすることです。

連想配列$_POSTには、先ほどのルーム作成画面のformタグから送信されてきたデータが格納されています。ここで重要なのが、$_POSTはルーム作成画面からpostされてきたデータであり、$_SESSIONはサーバに保存されているデータであるということです。つまり、$_SESSIONはサーバのデータが消えない限り値を保持し続けますが、$_POSTは他のページからのpost処理を踏んでこのページにアクセスしない限り空になるということです。ということは、$_POSTが空かどうか調べることで、ページへのダイレクトなアクセスは防ぐことができます。

さらに、ルーム作成画面以外からpostされてきた場合を防ぐには、$_SESSIONと$_POSTにそれぞれ保存されているトークンが一致するかどうかをチェックします。ルーム作成画面では$_SESSIONに保存したトークンと同一のものをpostしているので、正式なアクセスであればこれらは一致するはずです。これらが一致していないことは他のページからのアクセスを意味するため、拒絶します。

後者の条件は前者の条件を含んでいるので、後者についてのみ調べればOKです。

 

続いて、MySQLにデータを登録する部分です。ここでやりたいことは、固有のIDを割り当てたルームを作成し、ルームIDとパスワードをデータベースに保存することです。PHPMySQLをつなぐインタフェースとしてはPDOというAPIが主流なようですが、今回は直観的な分かりやすさからmysqliを使用しました。

ルーム毎に固有のIDを割り当てる方法ですが、今回とっているのはいたってシンプルな方法です。設定するIDの範囲を10000~20000と決めておき、10000から順番に、そのIDが既に登録されているかどうかをデータベースを探索して調べます。登録されていなければそのIDを使ってルームを作成します。

ルームに設定するパスワードは$_POSTで送られてきているのでこれを使います。セキュリティの観点から、パスワードはハッシュ化して保存しておきます。

 

最後に部屋を作成する部分ですが、下記の記事を参考にしました。

フォームで入力した内容をhtml ファイルを自動生成しながらphpで埋め込むコード |うつ病ブログ

ルーム入室画面(enterroom.php

 

f:id:Ito-you:20200823004157p:plain

ルーム入室画面

基本的にルーム作成画面と実装方法は変わらないため、ソースコードは割愛します。ボタンをクリックすることで、入力されたルームID、パスワード、CSRF対策のワンタイムトークンをルーム入室完了ファイル(enterroom_complete.php)へpostします。

ルーム入室完了ファイル(enterroom_complete.php

$roomid=$_POST['roomid'];
$password=$_POST['password'];

$_SESSION['csrf_token']=get_csrf_token();
$token=$_SESSION['csrf_token'];
?>

<form name='token' method='post' action="<?php echo 'room/'.$roomid?>.php">
    <input name='csrf_token' type='hidden' value='<?php echo $token ?>'>
</form>
<?php
$link=mysqli_connect('localhost',USER_NAME, PASSWORD, TABLE);
$hash=mysqli_fetch_array(mysqli_query($link,'SELECT password FROM room WHERE id='.$roomid.' LIMIT 1;'));
if(!empty($hash)){
    if(password_verify ( $password , $hash['password'] )){
        echo '<script>document.token.submit();</script>';
        exit();
    }else{
        $_SESSION['error_status']=2;
        redirect_to_enterroom();
        exit();
    }
}else{
    $_SESSION['error_status']=3;
    redirect_to_enterroom();
    exit(); 
}
ソースコード(一部抜粋)

 

このファイルはIDとパスワードの認証およびルームへのアクセスに用いられるため、ユーザに見せるための画面は存在しません。

各ルームへはこのファイルからのpost処理を介してしかアクセスできないようにするため、ここでもワンタイムトークンを設定しています。今まではボタンを押すことでpost処理をしていましたが、今回はパスワードの認証が終わり次第post処理を行います。

formタグのname属性に登録した名前でjavascriptのオブジェクトが作成されており、そのsubmitメソッドを呼び出すことでpostが行われるようです。

認証が失敗したり、ルームIDが登録されていなかったりした場合には、エラーコードを設定するとともにルーム入室画面へ強制的に移動します。

ルーム画面("ルームID".php

 

f:id:Ito-you:20200823014547p:plain

ルーム画面(ルームID10011)

まあ…わざわざ画像のせるまでもないですが(笑)

動かしてみる


バックエンドが少しでも触れるようになったことで実装できるものの幅がぐんと広がった気がします。

信用性を担保するならフレームワークとかAPIとかもっと使った方がいいんだとは思いますが、仕組みを理解するためにもまずは素朴に開発を進めようと思います。