Appleとのサインインクライアントからサーバ側への検証


私は今後のアプリと呼ばれるに取り組んでいた、私はアプリにユーザーをログにいくつかの方法が必要でした.しかし、私はその経験が本当に滑らかであることを望みました;私はサインアップやログインフォームをポップアップしたくない.それで、私はアップルとのサインがそれのために良い合うものであると思いました、それで、私はそれをプラスしてGoogleとサインするオプションを加えました.
ロジックはかなり簡単です.したら、これらのボタンのいずれかをタップすると、クライアントSDKを介して認証します.アップルとのサインについては😉), それはAuthenticationServices それを気にかけるフレームワーク、そしてGoogleについてGoogle Sign-In スウィフトパッケージ.認証されると、SDKは認証のためにあなたのバックエンドサーバに送ることができるトークンか認証コードを送ります.これは、検証に必要なすべての異なる要素を取得するために、あらかじめ設定をたくさん必要とするためです.成功した検証の後、データベースのユーザーの存在に応じて、JWTトークンに含まれる情報のデコードされた部分からユーザーを作成するかもしれません.彼がまだ存在しないならば、我々はユーザーテーブルで記録をつくって、RESTful API上の次の要求のために使うことができるアクセストークンでクライアントに彼を送り返します.
どのようにこの記事のすべてを行うを参照してください.このガイドを3つの主要なセクションに分けました.
  • クライアント側セットアップ
  • キー作成
  • サーバ側の検証
  • 興奮?それ以上のADOなしで右にジャンプしましょう.

    クライアント側セットアップ


    まず最初にアプリを作成することから始めましょう.それはSwiftuiを使用して単一のアプリケーションです.

    簡単にするために、ContentViewを使用して、すべてのコードをホストします😅). IOSの14は、名前を付けてアップルとのサインに便利な方法を追加SignInWithAppleButton これはその初期化器を通して直接設定可能です.
    SignInWithAppleButton(
      label: SignInWithAppleButton.Label, 
      onRequest: (ASAuthorizationAppleIDRequest) -> Void,
      onCompletion: ((Result<ASAuthorization, Error>) -> Void)
    )
    
    The onRequest and onCompletion 引数は両方のクロージャ、要求を構成する最初、結果を処理する2番目です.これらのメソッドをViewModelに置き、ロジックからビューを分離します.次のようになります.
    import SwiftUI
    import Combine
    import AuthenticationServices
    
    
    final class ViewModel: ObservableObject {
    
      func handle(request: ASAuthorizationAppleIDRequest) {
      }
    
      func handle(completion result: Result<ASAuthorization, Error>) {
      }
    
    }
    
    
    struct ContentView: View {
      @StateObject private var viewModel = ViewModel()
    
        var body: some View {
          VStack {
            Spacer()
            Text("👋 Hello SIWA")
              .font(.title)
            Spacer()
            SignInWithAppleButton(
              .continue,
              onRequest: viewModel.handle(request:),
              onCompletion: viewModel.handle(completion:)
            )
            .frame(height: 48)
          }.padding()
        }
    }
    
    認証要求の場合、ユーザに問い合わせるemailfullName .
    func handle(request: ASAuthorizationAppleIDRequest) {
      request.requestedScopes = [.fullName, .email]
    }
    
    プロジェクトの設定に移動し、メインターゲットを選択します“署名&機能”タブで、“アップル”機能でサインインを追加します.

    今、あなたはアプリを実行することができます.SIWAをテストするために物理的なデバイスを使用する必要があることに注意してくださいiOS 13シミュレータ以来未解決のバグです.
    とにかく、それはこのように見えるはずです.

    一度ボタンをタップすると、あなたはあなたの電子メールまたはあなたの本当のメールに転送し、同様に名前を設定するプライベートのいずれかを使用することができますシワポップアップがあります.
    注意してくださいこのポップアップは、顔のIDまたはタッチIDを介してそれを受け入れる前に一度だけ表示されますその後のログインのために、あなたは承認のためにもう一つのポップアップを持ちます.したがって、彼の名前のような情報のユーザーピースをしたい場合は、ポップアップが表示されるのは初めて処理する必要があります.しかし、あなたが台無しにするならば、私はあなたが後でポップアップをリセットすることができて、それが最初であったようにそれを示す方法をあなたに教えます.
    さて、一度我々が受け入れる(または否定する)ならば、ハンドル完成方法は解雇されます、そして、我々は合法化のために我々のバックエンドに提供される認可コードを送ることができます.
    final class ViewModel: ObservableObject {
    
      func handle(request: ASAuthorizationAppleIDRequest) {
        request.requestedScopes = [.fullName, .email]
      }
    
      func handle(completion result: Result<ASAuthorization, Error>) {
        switch result {
          case .success(let authorization):
            guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential,
            let tokenData = credential.authorizationCode,
            let token = String(data: tokenData, encoding: .utf8)
            else { print("error"); return }
            send(token: token)
          case .failure(let error):
            print(error.localizedDescription)
        }
      }
    
      private func send(token: String) {
        guard let authData = try? JSONEncoder().encode(["token": token]) else {
          return
        }
        let url = URL(string: "https://yourbackend.example.com/tokensignin")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    
        let task = URLSession.shared.uploadTask(with: request, from: authData) { data, response, error in
          // Handle response from your backend.
        }
        task.resume()
      }
    
    }
    
    それは、我々が我々のクライアントのためにする必要があるすべてです.それはかなり簡単です.では、クライアントが生成したトークンを検証するのに役立つ公開鍵と秘密鍵の設定を処理しましょう.

    キーの作成と設定


    では、クライアントの認証コードを確認するためにキー(public and secret)を作成しましょう.
  • 移動するdeveloper.apple.com アカウントにログイン
  • 左サイドバーの「証明書、IDとプロフィール」をクリックしてください
  • ページが読み込まれると、新しいキーを作成するには
  • をクリックして“+”アイコンを、キーの名前を入れて、私はそれを呼び出すつもりですsiwatut
  • チェックボックスをクリックし、「構成」ボタンをクリックします
  • それはキー構成ページにあなたをリダイレクトし、キーを作成するアプリを選ぶための選択したtextFieldが表示されます.アプリを選択し、“保存”ボタンをクリックします.
  • それはキーの設定ページに戻ってあなたをリダイレクトします
  • キーの作成の概要を参照してください
  • 今、ステップ本当に重要な、あなたのコンピュータ上のローカルキーを保存するために“ダウンロード”ボタンをクリックする必要があります.私はあなたのディスクのどこかで安全に保存することをお勧めします.

  • 秘密鍵の名前を変更するAuthKey_keyid.p8 to key.txt
  • さあ、端末を開けてkey.txt
  • JWTの宝石をインストールしますsudo gem install jwt
  • 完了したら、ファイルを作成しますclient_secret.rb プライベートキーを処理し、お気に入りのテキストエディタで開くには
  • その内容を入れてください.
  • require 'jwt'
    
    key_file = 'key.txt'
    team_id = 'your-team-id'
    client_id = 'dev.ibrahima.siwa-tut'
    key_id = 'your-key-id'
    
    ecdsa_key = OpenSSL::PKey::EC.new IO.read key_file
    
    headers = {
    'kid' => key_id
    }
    
    claims = {
        'iss' => team_id,
        'iat' => Time.now.to_i,
        'exp' => Time.now.to_i + 86400*180,
        'aud' => 'https://appleid.apple.com',
        'sub' => client_id,
    }
    
    token = JWT.encode claims, ecdsa_key, 'ES256', headers
    
    puts token
    
  • team_id あなたのアップル開発者アカウントにログインしたときに右上隅に見つけることができます
  • client_id 我々のアプリのバンドル識別子ですか
  • key_id 上記のステップ9で作成された秘密鍵識別子
  • ファイルを保存し、端末に戻ってコマンドを入力します
  • ruby client_secret.rb
    
    すべてうまくいけば、端末にJWTトークンを印刷するべきです.

    コピーして、その間にどこかに保存し、我々はバックエンドでそれを使用します.
    JWTトークンを生成したので、必要な情報を抽出するためにバックエンドで処理しましょう.

    サーバ側の検証


    バックエンドのために、私は使用するつもりですLaravel PHPフレームワーク.もちろん、任意のサーバーサイドフレームワーク(Express、Django、レールなど)を選択することができます.私はすでにララベルの新しいインストールを持っている.
    さあ、以前に生成したトークンをコピーします.env , 以下のような環境ファイル
    SIWA_CLIENT_ID=dev.ibrahima.siwa-tut # your xcode bundle identifer used to generate the token
    SIWA_CLIENT_SECRET= # the jwt token generated earlier
    SIWA_GRANT_TYPE=authorization_code # used in the validation request
    
    クライアントがヒットするエンドポイントを追加しましょう.私はウェブに行きます.PHPルートファイル、およびリクエストを処理するコントローラを作成します.
    // web.php
    Route::post('tokensignin', [AuthController::class, 'handleSIWALogin']);
    
    // AuthController.php
    
    <?php
    
    namespace App\Http\Controllers;
    
    use App\Models\User;
    use GuzzleHttp\Client;
    use GuzzleHttp\Psr7\Request;
    use Illuminate\Http\JsonResponse;
    use GuzzleHttp\Exception\GuzzleException;
    
    
    class AuthController extends Controller
    {
    
        public function handleSIWALogin()
        {
            $authorizationCode = request()->input('token'); // 1
            $body = 'client_id=' . env('SIWA_CLIENT_ID') . '&client_secret=' . env('SIWA_CLIENT_SECRET') . '&code=' . $authorizationCode . '&grant_type=authorization_code';
            $client = new Client();
            $request = new Request("POST", "https://appleid.apple.com/auth/token", ["Content-Type" => "application/x-www-form-urlencoded"], $body); // 2
            try {
                $response = $client->send($request); // 3
                $data = json_decode($response->getBody(), true);
                $payload = json_decode(base64_decode(str_replace('_', '/', str_replace('-', '+', explode('.', $data['id_token'])[1]))), true); // 4
                if ($payload['email']) return $this->createOrLogUser($payload); // 5
                return $this->respondWithError("Could not authenticate with this token");
            } catch (GuzzleException $e) {
                return $this->respondWithError($e->getMessage()); // 6
            }
        }
    
        private function createOrLogUser($payload): JsonResponse
        {
            // create the user if he's not yet in the database or return him with (if he already exists) with a token and send it back to the client
        }
    
        private function respondWithError($message): JsonResponse
        {
            // return a json response representing an error
        }
    }
    
    
    上記のコードに脅かされないでください、そして、それはとても簡単です.トークンを確認するために必要な異なる手順を見てみましょう.
  • クライアントが送信したトークンを取得する
  • 我々は、トークンを認証するためにアップルのサーバーに送られる要求を構築します.あなたが見つけるhere パラメータのリスト
  • 依頼を送る
  • リクエストが成功した場合は、json_decode メソッドid_token フィールド
  • 抽出された電子メールで、我々はユーザーをログに登録するか、データベースで彼をつくって、次に、認証トークンをクライアントに送ります
  • 何かが間違っているなら、我々は誤りを返します.
    クライアントは、後続の要求に対する認証のために役立つバックエンドによって生成されたトークンでログインする必要があります.
    あなたは、テスト目的のためにXcodeの上で認可コードを印刷することができて、以前に作成されるすべての資格証明でアップルサーバにポストリクエストをすることができます.結果は次のようになります.

  • アップル認可ポップアップでサインインをリセットすること。


    私が以前あなたに言ったように、あなたがあなたの名前を与えて、個人的な電子メールを共有するか、使うと決める認可ポップアップは、一度表示されます.受け入れた後、その後の承認のための別のポップアップがあります.私は、あなたがテストのために彼の名前のようにすべてのユーザーデータを検索したいかもしれないということを知っています、しかし、あなたが初めてそれをしなかったならば、あなたは後でそれをする機会がありません.あなたはこれらの手順に従って、アップル認定ポップアップでログインをリセットすることができます.
  • お使いの携帯電話の設定アプリに移動
  • トップであなたのiCloudプロファイルを選択してください
  • パスワードとセキュリティ
  • “アップルIDを使用してアプリ”に移動
  • を選択して懸念アプリをタップして“アップルIDを使用して停止”
    それです.あなたが承認したい次回は、それはあなたの元のポップアップを共有するかどうか、あなたの本当の電子メールとあなたの名前をカスタマイズするオプションを与えるもたらすよ.
  • 最後の思考


    ご覧のように、Appleとのサインインによって提供された認証コードを検証するのは困難ではありません😔. 詳細については、Apple Docsからユーザー確認とトークン検証に関するドキュメントを読むことをお勧めします.
  • Verifying a user

  • Token generation and validation
    この記事は役に立つと思います.何か質問があれば教えてください.