Laravel Контроллер Oauth2 с использованием клиента League OAuth2 - PullRequest
3 голосов
/ 05 мая 2020

Я пытаюсь использовать клиент OAuth2 League , чтобы пользователи могли аутентифицировать мое Laravel веб-приложение для назначения встреч в своем календаре. ПРИМЕЧАНИЕ. Я не пытаюсь разрешить пользователям входить на мой сайт или проходить аутентификацию на моем сайте с помощью OAuth! Я просто хочу, чтобы пользователи могли добавлять встречи в свои собственные календари.

Я в основном следую схеме, описанной здесь: https://github.com/thephpleague/oauth2-google и создал один контроллер (называемый OauthController одним методом, redirectGoogle. Мой маршрут перенаправления (который зарегистрирован в Google) - https://example.com/oauth2/google. Когда я попадаю в эту конечную точку в моем приложении Laravel, я перенаправляюсь в Google, чтобы разрешить моему приложению доступ к моему данные учетной записи, как и ожидалось, а затем перенаправляются обратно в конечную точку контроллера.

Однако каждый раз происходит сбой в строке exit('Invalid state');.

Вот код метода контроллера:

public function redirectGoogle(Request $request)
{
    $provider = new Google([
        'clientId'     => config('oauth.google_oauth_id'),
        'clientSecret' => config('oauth.google_oauth_secret'),
        'redirectUri'  => 'https://example.com/oauth2/google',
    ]);

    if (!empty($request->input('error'))) {
        // Got an error, probably user denied access
        dd($request->input('error'));
    } elseif (empty($request->input('code'))) {
        // If we don't have an authorization code then get one
        $authUrl = $provider->getAuthorizationUrl();
        session(['oauth2state', $provider->getState()]);
        Log::info('Storing provider state ' . session('oauth2state')); <-- Log entry exists so we know session value was written
        header('Location: ' . $authUrl);
        exit;
    } elseif (empty($request->input('state')) || ($request->input('state') !== session('oauth2state', false))) {
        Log::error($request->input('state') . ' did not equal stored value ' . session('oauth2state', false)); <-- Log entry exists
        // State is invalid, possible CSRF attack in progress
        exit('Invalid state'); <-- Breaks here
    } else {
        // Try to get an access token (using the authorization code grant)
        $token = $provider->getAccessToken('authorization_code', [
            'code' => $request->input('code')
        ]);

        // Optional: Now you have a token you can look up a users profile data
        try {
            // We got an access token, let's now get the owner details
            $ownerDetails = $provider->getResourceOwner($token);

            // Use these details to create a new profile
            dd('Hello %s!', $ownerDetails->getFirstName());

        } catch (Exception $e) {
            // Failed to get user details
            dd('Something went wrong: ' . $e->getMessage());
        }

        // Use this to interact with an API on the users behalf
        echo $token->getToken() . PHP_EOL;

        // Use this to get a new access token if the old one expires
        echo $token->getRefreshToken() . PHP_EOL;

        // Unix timestamp at which the access token expires
        echo $token->getExpires() . PHP_EOL;
        dd();
    }
}

Странно то, что сообщения журнала, указанные в приведенном выше коде, существуют, и значения совпадают (по крайней мере, он пытается записать первую переменную сеанса со значением, которое соответствовало бы значению второго файла журнала):

[2020-05-04 21:02:48] local.INFO: Storing provider state 4963a33bbd5bcf52d3e21c787f24bd7b  
[2020-05-04 21:02:51] local.ERROR: 4963a33bbd5bcf52d3e21c787f24bd7b did not equal stored value <null>

Почему во второй раз в коде значение сеанса oauth2state равно нулю, когда оно было успешно записано на первом l oop?

ПРИМЕЧАНИЕ: проблема, по-видимому, в том, что сеансы разные, что имеет смысл, но как этот сеанс может оставаться согласованным или иным образом поддерживать прямые данные?

[2020-05-05 15:25:06] local.INFO: Session id: bV7F5mNM69rJAVJNWK9ZD0rcoN284FxXvjNAmUiw  
[2020-05-05 15:25:06] local.INFO: Storing provider state 7351b313b741df41a6be9a049f71db6b  
[2020-05-05 15:25:10] local.INFO: Session id: VNiBxr1gYYIA9Nr11x9c4JJArHOiKQScEGh2jkuc  
[2020-05-05 15:25:10] local.ERROR: 7351b313b741df41a6be9a049f71db6b did not equal stored value <null>  

EDIT2: Я пробовал учебник здесь , в котором используется немного другой подход с использованием Laravel и библиотеки League Oauth - у него точно такая же проблема, сеанс Идентификатор в двух запросах различается, что означает, что вы никогда не сможете найти совпадение между ключами state.

1 Ответ

1 голос
/ 08 мая 2020

Я считаю, что проблема заключается в том, как вы перенаправляете в Google.

Проблема :

Laravel необходимо выполнить через весь запрос, чтобы сохранить значения в сеанс.

Используя exit;, вы прерываете запрос и, следовательно, Laravel не будет иметь возможности сохранить значения в сеансе.

Решение :

Используя помощник redirect(), как предложено в документах , Laravel сможет выполнить запрос.

elseif(empty($request->input('code'))) {
    // If we don't have an authorization code then get one
    $authUrl = $provider->getAuthorizationUrl();
    session(['oauth2state', $provider->getState()]);
    Log::info('Storing provider state ' . session('oauth2state'));
    return redirect($authUrl);
}

Пояснение :

В Laravel вы можете решить, когда будет запускаться промежуточное ПО, из документации :

До и после промежуточного ПО

Будет ли промежуточное ПО запускаться до или после запроса, зависит от самого промежуточного ПО. Например, следующее промежуточное программное обеспечение выполнит некоторую задачу до того, как запрос будет обработан приложением:

public function handle($request, Closure $next)
{
    // Perform action

    return $next($request);
}

Однако это промежуточное ПО будет выполнять свою задачу после того, как запрос будет обработан приложением. :

public function handle($request, Closure $next)
{
    $response = $next($request);

    // Perform action

    return $response;
}

Теперь, если мы посмотрим, как Laravel сохраняет данные сеанса в промежуточном программном обеспечении StartSession , вы увидите здесь , что Laravel пытается сохранить данные в сеансе после того, как запрос был обработан приложением, поэтому, используя exit;, die(); или dd();, вы останавливаете скрипт, а Laravel никогда не получает возможность сохранить значения в сеансе.

protected function handleStatefulRequest(Request $request, $session, Closure $next)
{
    // Before middleware
    $request->setLaravelSession(
        $this->startSession($request, $session)
    );

    $this->collectGarbage($session);

    $response = $next($request);

    // After middleware
    $this->storeCurrentUrl($request, $session);

    $this->addCookieToResponse($response, $session);

    $this->saveSession($request);

    return $response;
}
...