Laravel 實作 Google 第三方登入


建立時間: 2024年3月12日 14:00
更新時間: 2024年3月28日 03:00

說明

本篇將分享 Laravel 實作 Google 登入程式的部分,除了最基本的在同個網域之下進行之外,我將另外分享如何跨域來實現 Google 登入。

前置作業

你需要先完成 Google API 服務的設定,最終你會取得

GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET

Laravel Socialite

使用 Laravel Socialite 實現 Google OAuth Provider 登入。使用上相當方便,但缺點是不能跨站登入,也就是說我只能在自己的網站做登入。

https://domain.com/login/auth/google
https://domain.com/login/auth/google/redirect
https://domain.com

如上範例,我只能在 domain.com 驗證、登入。

安裝

composer require laravel/socialite

設定

config\services.php

<?php

return [
    'google' => [
        'client_id' => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        // redirect to callback rul
        'redirect' => env('GOOGLE_REDIRECT'),
    ],
]

.env

GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
GOOGLE_REDIRECT="http://localhost:8000/auth/callback"

用 env 是方便在環境變數設定,你可以按照自己的偏好的方式設定,redirect 是 Google callback 網址,下方 Route 會設定 Google callback 路由。

Google 憑證

在 Google 憑證 > OAuth 2.0 用戶端 ID > 已授權的重新導向 URI 記得也加上 Google callback url。

Google callback url settings

以本範例來說就是 http://localhost:8000/auth/callback

Routing

要使用 OAuth Provider 來登入使用者,需要兩個 Route:一個用來將使用者重新導向到 OAuth Provider,另一個用來接收登入後 Provider 傳回來的回呼。

這裡使用最基本的範例給大家參考

routes\web.php

use Laravel\Socialite\Facades\Socialite;

Route::get('/auth/redirect', function () {
    return Socialite::driver('google')->redirect();
});

Route::get('/auth/callback', function () {
    $user = Socialite::driver('google')->user();
});

Socialite Facade 上的 redirect 方法負責將使用者重新導向到 OAuth Provider。當使用者同意登入要求後,user 方法會檢查傳入的 Request,並向 OAuth Provider 取得使用者的資訊。

先到 /auth/redirect 跳出 Google 登入視窗,登入後會重導向到之前設定的 GOOGLE_REDIRECT url,本範例會前往 /auth/callback,可以看到取得使用者資訊的程式碼。

跨站登入流程說明

跨站登入的流程為在前端使用 JavaScript 實作登入功能,並取得 Google 提供的驗證資料,像是 access token,code,jwt token,再將資料傳給後端做驗證,後端取得前端傳過來的資料,再跟 Google 驗證資料是否是真的 Google 的資料,如果驗證成功,就代表後端可以取得使用者資訊了。

  1. 前端登入,取得 Google 驗證資料。
  2. 呼叫後端 Google 登入 API,請求資料帶入 Google 驗證資料。
  3. 後端跟 Google 驗證資料是否正確。
  4. 若正確,使用前端帶過來的資料取得使用者資訊。

設定已授權的 JavaScript 來源

若要用 JavaScript 登入,記得要在 Google 憑證 > 已授權的 JavaScript 來源設定前端登入的網址

Authorized javaScript source

假設是 http://localhost:1234/login 那就是新增一筆 URI 填寫頂級網域 http://localhost:1234

安裝 Google API Client 套件

在使用 Google JWT 或將 code 交換有效的授權權杖會需要使用 Google API Client 套件,很可惜我沒有在 Socialite 找到相關的方法可以處理這些事情。

安裝方式使用 Composer 安裝:

composer require google/apiclient

JWT 登入

前端 Google 登入取得 JWT(JSON Web Token),後端驗證 token,驗證成功後取得 payload。

註: JWT 就是編碼過的 json 資料。
註: payload 中文是有效負載,你可以當作是 JWT 解析出來的資料。

  • handleCredentialResponse() 的 url 改成自己設定的 api 網址。
  • client_id 改成自己的 Google 憑證 client id。
  • params 是我自定義的,你可以自行更改你想要的參數。
<!DOCTYPE html>
<html lang="zh-Hant">

<head>
    <meta charset="utf-8">
    <meta name="viewport"
          content="width=device-width, initial-scale=1">
    <meta name="referrer"
          content="strict-origin-when-cross-origin">
    <meta name="Cross-Origin-Opener-Policy"
          content="same-origin-allow-popups">

    <title>JWT Login</title>

    <script src="https://accounts.google.com/gsi/client"
            async
            defer></script>
</head>

<body>
    <script>
        function handleCredentialResponse(response) {
            console.log("Encoded JWT ID: " + response.credential);

            const params = new URLSearchParams({
                Token: response.credential,
            });
            const url = new URL('http://localhost:8000/api/login/auth/google/jwt');

            url.search = params;

            // 使用 fetch 函數發送 GET 請求
            fetch(url.href)
                .then(response => {
                    // 檢查回應的狀態碼
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    // 將回應解析為 JSON
                    return response.json();
                })
                .then(data => {
                    // 將 JSON 資料輸出到控制台
                    console.log(data);
                    console.log('Welcome.');
                })
                .catch(error => {
                    // 捕獲錯誤並輸出到控制台
                    console.error('There was a problem with the fetch operation:', error);
                });
        }

        window.onload = function() {
            google.accounts.id.initialize({
                client_id: 'your-client-id',
                callback: handleCredentialResponse
            });
            google.accounts.id.renderButton(
                document.getElementById("buttonDiv"), {
                    theme: "outline",
                    size: "large"
                }
            );
            google.accounts.id.prompt();
        }
    </script>
    <div id="buttonDiv"></div>
</body>

</html>

後端 JWT 驗證

後端驗證 JWT 的程式碼如下,你可以將這段程式碼,你可以將這段程式碼放在合適的地方,例如: Controller,Service 等等,當驗證通過之後,你就能取得 payload 進行後續的工作,最後你就能回應資訊給前端。

use Google\Client;

// Get $id_token via HTTPS POST.

$client = new Client();
$client->setClientId(config('services.google.client_id'));
$payload = $client->verifyIdToken($id_token);

if ($payload) {
  $userid = $payload['sub'];
  // If request specified a G Suite domain:
  //$domain = $payload['hd'];
} else {
  // Invalid ID token
}

這裡使用的 config('services.google.client_id') 是之前在使用 Socialite 所設定的。

Access Token 登入

前端 Google 登入取得 access token,後端驗證 token,驗證成功後取得使用者資料。

  • new URL() 的 url 改成自己設定的 api 網址。
  • client_id 改成自己的 Google 憑證 client id。
  • scope: 要取得使用者資訊的範疇,一般大概就是範例程式碼的這兩個,多筆可以空格區隔。
  • params 是我自定義的,你可以自行更改你想要的參數。
<!DOCTYPE html>
<html lang="zh-Hant">

<head>
    <meta charset="utf-8">
    <meta name="viewport"
          content="width=device-width, initial-scale=1">
    <meta name="referrer"
          content="strict-origin-when-cross-origin">
    <meta name="Cross-Origin-Opener-Policy"
          content="same-origin-allow-popups">

    <title>Access Token Login</title>

    <script src="https://accounts.google.com/gsi/client"></script>
</head>

<body>
    <script>
        function login(accessToken) {
            const params = new URLSearchParams({
                Token: accessToken,
            });
            const url = new URL('http://localhost:8000/api/login/auth/google/access-token');

            url.search = params;

            // 使用 fetch 函數發送 GET 請求
            fetch(url.href)
                .then(response => {
                    // 檢查回應的狀態碼
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    // 將回應解析為 JSON
                    return response.json();
                })
                .then(data => {
                    // 將 JSON 資料輸出到控制台
                    console.log(data);
                    console.log('Welcome.');
                })
                .catch(error => {
                    // 捕獲錯誤並輸出到控制台
                    console.error('There was a problem with the fetch operation:', error);
                });
        }

        const client = google.accounts.oauth2.initTokenClient({
            client_id: 'your-client-id',
            scope: 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
            callback: (response) => {
                console.log(response);
                const token = response.access_token;

                login(token);
            },
        });
    </script>
    <button onclick="client.requestAccessToken();">Authorize me</button>
</body>

</html>

後端 Access Token 驗證

access token 驗證可以使用 Laravel Socialite 取得使用者資訊。

use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\GoogleProvider;

/** @var GoogleProvider $googleDriver */
$googleDriver = Socialite::driver('google');
$user = $googleDriver->userFromToken($token);

Code 登入

Code 就是授權碼,前端 Google 登入取得 code,後端驗證 code,驗證成功後取得 access token,接著再用 access token 取得使用者資料。

  • client_id: 改成自己的 Google 憑證 client id。
  • redirect_url: 前端重新導向的 url。
  • scope: 要取得使用者資訊的範疇。
  • ux_mode: Google 登入的情境,範例採用 redirect,也就是重新導向的方式。
  • const url: 改成自己設定的 api url。
<!DOCTYPE html>
<html lang="zh-Hant">

<head>
    <meta charset="utf-8">
    <meta name="viewport"
          content="width=device-width, initial-scale=1">
    <meta name="referrer"
          content="strict-origin-when-cross-origin">
    <meta name="Cross-Origin-Opener-Policy"
          content="same-origin-allow-popups">

    <title>Laravel</title>

    <script src="https://accounts.google.com/gsi/client"></script>
</head>

<body>
    <script>
        const tokenClient = google.accounts.oauth2.initCodeClient({
            client_id: 'your-client-id',
            redirect_uri: 'http://localhost:1234/login',
            scope: 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email',
            ux_mode: 'redirect',
        });

        let urlParams = new URLSearchParams(window.location.search);

        if (urlParams.has('code')) {
            const code = urlParams.get('code');
            const params = new URLSearchParams({
                Code: code,
            });
            const url = new URL('http://localhost:8000/api/login/auth/google/code');

            url.search = params;

            // 使用 fetch 函數發送 GET 請求
            fetch(url.href)
                .then(response => {
                    // 檢查回應的狀態碼
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    // 將回應解析為 JSON
                    return response.json();
                })
                .then(data => {
                    // 將 JSON 資料輸出到控制台
                    console.log(data);
                    console.log('Welcome.');
                })
                .catch(error => {
                    // 捕獲錯誤並輸出到控制台
                    console.error('There was a problem with the fetch operation:', error);
                });
        }
    </script>
    <button onclick="tokenClient.requestCode();">Authorize me</button>
</body>

</html>

後端 Code 驗證

使用 Google Client 驗證 code,接著再取得 access token,然後再用 access token 驗證的方式取得使用者資料。

use Google\Client;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\GoogleProvider;

$client = new Client();
$client->setClientId(config('services.google.client_id'));
$client->setClientSecret(config('services.google.client_secret'));
$client->setRedirectUri('http://localhost:1234/login');
$response = $client->fetchAccessTokenWithAuthCode($code);
$accessToken = $response['access_token'];

/** @var GoogleProvider $googleDriver */
$googleDriver = Socialite::driver('google');
$user = $googleDriver->userFromToken($accessToken);

參考

觀看次數: 1459
apigooglelaravelloginpartythird
按讚追蹤 Enjoy 軟體 Facebook 粉絲專頁
每週分享資訊技術

一杯咖啡的力量,勝過千言萬語的感謝。

支持我一杯咖啡,讓我繼續創作優質內容,與您分享更多知識與樂趣!