简体   繁体   English

Laravel Oauth2 controller 使用 League OAuth2 客户端

[英]Laravel Oauth2 controller using League OAuth2 client

I'm trying to use the League OAuth2 Client to allow users to authenticate my Laravel web app to set appointments on their calendar.我正在尝试使用联盟 OAuth2 客户端允许用户验证我的 Laravel web 应用程序以在他们的日历上设置约会。 NOTE: I'm not trying to let users login to my site or authenticate into my site using OAuth. I just want to be able to let users add appointments to their own calendars.注意:我并不想让用户使用 OAuth 登录我的网站或验证我的网站。我只是想让用户能够将约会添加到他们自己的日历中。

I'm basically following the flow outlined here: https://github.com/thephpleague/oauth2-google and have created a single controller (called OauthController with a single method, redirectGoogle . My redirect route (which is registered with Google) is https://example.com/oauth2/google . When I hit this endpoint in my Laravel app, I get redirected to Google to approve my app to access my account data as expected, and then redirected back to the controller endpoint.我基本上遵循此处概述的流程: https://github.com/thephpleague/oauth2-google并创建了一个 controller(使用单一方法redirectGoogle称为OauthController 。我的重定向路由(已在 Google 注册)是https://example.com/oauth2/google 。当我在我的 Laravel 应用程序中点击此端点时,我被重定向到 Google 以批准我的应用程序按预期访问我的帐户数据,然后重定向回 controller 端点。

However it fails every time at the exit('Invalid state');但是它每次都在exit('Invalid state'); line.线。

Here's the controller method code:这是 controller 方法代码:

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();
    }
}

The strange thing is that the log messages noted in the code above both exist, and the values match (at least, it is attempting to write the first session variable with a value that would match the second log file's value):奇怪的是,上面代码中记录的日志消息都存在,并且值匹配(至少,它试图写入第一个 session 变量,其值与第二个日志文件的值匹配):

[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>

Why is it that the second time through the code the oauth2state session value is null, when it was successfully written on the first loop?为什么第二次通过代码oauth2state session 值是 null,而它是在第一次循环中成功写入的?

NOTE: the problem appears to be that the sessions are different, which makes sense, but how can this session stay consistent, or otherwise keep the data straight?注意:问题似乎是会话不同,这是有道理的,但是这个 session 如何保持一致,或者以其他方式保持数据直接?

[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: I've tried the tutorial here which uses a slightly different approach using Laravel and the League Oauth library-- it has the exact same problem, the session ID is different between the two requests, meaning there's no way you'll ever get a match between the state keys. EDIT2:我在这里尝试了教程,它使用 Laravel 和 League Oauth 库使用略有不同的方法——它有完全相同的问题,两个请求之间的 session ID 不同,这意味着你永远无法获得state键之间的匹配。

I believe the problem lies with how you redirect to google.我认为问题在于您如何重定向到谷歌。

Problem :问题

Laravel needs to run trough the whole request in order to persist values into the session. Laravel 需要运行整个请求才能将值持久化到 session 中。

By using exit;通过使用exit; you are interrupting the request and therefore Laravel will not get the chance to persist the values into the session.您正在中断请求,因此 Laravel 将没有机会将值保存到 session 中。

Solution :解决方案

By using the redirect() helper as suggested in the docs , Laravel will be able to complete the request.通过使用文档中建议的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);
}

Explanation :解释

In Laravel you can decide when a middleware is run, from the docs :在 Laravel 中,您可以从文档中决定何时运行中间件:

Before & After Middleware中间件前后

Whether a middleware runs before or after a request depends on the middleware itself.中间件是在请求之前还是之后运行取决于中间件本身。 For example, the following middleware would perform some task before the request is handled by the application:例如,以下中间件将在应用程序处理请求之前执行一些任务:

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

    return $next($request);
}

However, this middleware would perform its task after the request is handled by the application:但是,此中间件将在应用程序处理请求后执行其任务:

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

    // Perform action

    return $response;
}

Now if we take a look at how Laravel persists the session data in the StartSession middleware , you can see here that Laravel tries to persist the data into the session after the request has been handled by the application, so by using exit;现在,如果我们看一下 Laravel 如何在StartSession 中间件中持久保存 session 数据,您可以在此处看到 Laravel 尝试在应用程序处理请求后将数据持久保存到 session 中,因此通过使用exit; , die(); , die(); or dd();dd(); your are stopping the script and Laravel never gets the opportunity to persist the values in the session.您正在停止脚本,Laravel 永远不会有机会保留 session 中的值。

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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM