Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

usage of oauth-scope-approval #55

Open
dreamsbond opened this issue Sep 7, 2018 · 45 comments
Open

usage of oauth-scope-approval #55

dreamsbond opened this issue Sep 7, 2018 · 45 comments

Comments

@dreamsbond
Copy link

APPROVAL_URI and ERROR_URI constant are pointed to "oauth-scope-approval"

i would like to know the usage implementing this route.

please help

@dreamsbond
Copy link
Author

besides, i cannot find where the client_secret to set and get

please could you help?

@neomerx
Copy link
Collaborator

neomerx commented Sep 7, 2018

Do you mean KEY_APPROVAL_URI_STRING? After a 3rd party server redirects a user to limoncello server it should show the user a list of scopes/rights/permissions requested by the 3rd party server. The user can a) accept all requested scopes b) edit and grant only a few scopes c) reject the request. The approval URI value is used in createAskResourceOwnerForApprovalResponse where it packs all the data in the URL and makes a redirect. It is used in Code Grant and Implicit Grant. The part you'are asking for is described in 4.1.1 of the RFC as

the authorization server authenticates the resource owner and obtains an authorization decision (by asking the resource owner or by establishing approval via other means).

The form should take the data (requested scope, if it midified from the default for this client, client state, etc) and present it to the user. Note you should check it was redirected from your server as rogue clients may bypass you and redirect clients directly to this form. If the resource owner grants access (see 4.1.2) then server should redirect the user to 3rd party server.

@neomerx
Copy link
Collaborator

neomerx commented Sep 7, 2018

It's called clientCredentials. You can set the value on when create a client. By default it is verified with
PHP password_verify method in \Limoncello\Passport\Integration\BasePassportServerIntegration::verifyClientCredentials so it is expected to be password hash made by default PHP methods. Though you can replace verifyClientCredentials with custom implementation and store anything you want in credentials.

@dreamsbond
Copy link
Author

dreamsbond commented Sep 10, 2018

sorry. i found i was still confused about the setting in code grant basis on limoncello-app

to my understanding :

  1. i got 2 limoncello-app (A&B), A as a resource owner (http://localhost:8001), B as a authentication server (http://localhost)
  2. authorize via the http://localhost/authorize?client_id=default_client&redirect_url=http://localhost:8001
  3. get me to "APPROVAL_URI"
  4. any errors in between will get me to "ERROR_URI"

am i wrong?

on point 3, i am not sure if limoncello-app has given me the ability to approve. Is it built-in already or i have to build one for approving request?

i did some search on laravel about similar scenario, that is what i found:

Requesting Tokens
Redirecting For Authorization
Once a client has been created, developers may use their client ID and secret to request an authorization code and access token from your application. First, the consuming application should make a redirect request to your application's /oauth/authorize route like so:

Route::get('/redirect', function () {
    $query = http_build_query([
        'client_id' => 'client-id',
        'redirect_uri' => 'http://example.com/callback',
        'response_type' => 'code',
        'scope' => '',
    ]);

    return redirect('http://your-app.com/oauth/authorize?'.$query);
});
Remember, the /oauth/authorize route is already defined by the Passport::routes method. You do not need to manually define this route.
Approving The Request
When receiving authorization requests, Passport will automatically display a template to the user allowing them to approve or deny the authorization request. If they approve the request, they will be redirected back to the redirect_uri that was specified by the consuming application. The redirect_uri must match the redirect URL that was specified when the client was created.

If you would like to customize the authorization approval screen, you may publish Passport's views using the vendor:publish Artisan command. The published views will be placed in  resources/views/vendor/passport:

php artisan vendor:publish --tag=passport-views
Converting Authorization Codes To Access Tokens
If the user approves the authorization request, they will be redirected back to the consuming application. The consumer should then issue a POST request to your application to request an access token. The request should include the authorization code that was issued by your application when the user approved the authorization request. In this example, we'll use the Guzzle HTTP library to make the POST request:

Route::get('/callback', function (Request $request) {
    $http = new GuzzleHttp\Client;

    $response = $http->post('http://your-app.com/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => 'client-id',
            'client_secret' => 'client-secret',
            'redirect_uri' => 'http://example.com/callback',
            'code' => $request->code,
        ],
    ]);

    return json_decode((string) $response->getBody(), true);
});
This /oauth/token route will return a JSON response containing access_token, refresh_token, and expires_in attributes. The expires_in attribute contains the number of seconds until the access token expires.

Like the /oauth/authorize route, the /oauth/token route is defined for you by the  Passport::routes method. There is no need to manually define this route.

@dreamsbond
Copy link
Author

dreamsbond commented Sep 10, 2018

i think there was no route and method being implemented for KEY_APPROVAL_URI_STRING which defaults to "oauth-scope-auth" for approving request, as laravel passport do.

Will you consider putting similar view/interface in limoncello-app?

@dreamsbond
Copy link
Author

i search through the limoncello app and framework, still cannot locate the method for Authorization Response. can you help?

@neomerx
Copy link
Collaborator

neomerx commented Sep 11, 2018

I haven't tried to use limoncello in this scenario. Is it possible for you to publish a test project where I can test it and made necessary changes in the framework?

@dreamsbond
Copy link
Author

Another thing I observed is that. While I am doing code grant authorization. I can make authorize request at /authorize endpoint. But I have no where to generate a authorization code thru oauth-scope-approval. Is there some where I could do similar to authenticateByUserId. So then I could proceed to get the token from /token endpoint.

@neomerx
Copy link
Collaborator

neomerx commented Sep 12, 2018

@dreamsbond I'm extremely busy this week and most of the next week. If you create a test project with 2 folders client and server each of them configured as an OAuth client and OAuth server and show me where you've got an issue I think it can be quickly fixed.

@dreamsbond
Copy link
Author

after a more deeper looked into the framework, i wrote a few lines to create authorization code as:
https://github.com/dreamsbond/app/blob/dd3dbf0368069b91bea5ca2959546bc7ce49e434/server/app/Web/Controllers/AuthController.php#L205-L237

image

image

i can then reached the redirect_uri callback.

and the authorization code was successfully written:

image

so now, i have no idea about how could i make the authorization code expose as the query parameters into the redirect_url and also how to talk to the /token for authorization code <-> token exchange.

the flow for me to follow on Code Grant:

image

@neomerx
Copy link
Collaborator

neomerx commented Sep 20, 2018

I'll help you. I'm creating a simple client and a server that work with OAuth. Though I cannot help as quickly as I usually do as I'm very busy until the end of the month, however, I hope I should get some time in the evenings.

@dreamsbond
Copy link
Author

dreamsbond commented Sep 20, 2018

I think I start managed to work with the client (webpack) and linkage between the view and twig.

But I still was not yet managed to work with managing scopes and make approval thru client like in laravel passport. It beyond my understanding to the code in current stage. I will try look more deeper between limoncello app and framwork. Hope to get more inspiration.

@neomerx
Copy link
Collaborator

neomerx commented Sep 20, 2018

This branch was not field tested so don't worry at the moment that you don't understand it. It's likely not to be as it should be. I'm currently looking into this issue and making tweaks here and there to make code authorization sensible.

@neomerx
Copy link
Collaborator

neomerx commented Sep 20, 2018

I have an initial version (very very beta really) of a server and a client.

If you run the server and login
image

and then open the client in a separate tab
image

when you click the login button it redirects you to the server where you're already logged in (sorry logging in after the redirect is not there for simplicity) you will see default scopes assigned to the client
image
(sorry for the fonts 😄 and absense of Deny button (again for simplicity) ) Also it shows 'sign-in' button though you're logged in and the server knows who you are.

When you click Confirm it generates an authorization code and redirects it back to client. The client handler currently fails but it's complete Part One. The part two should also be almost complete as exchange of the code has some tests.

I can publish it in its current state so you can play with it. Or you can wait until I polish it (though as I said I'm really busy for the next couple of weeks so it might take time even I think it's not much to do).

image

note the code is generated

@dreamsbond
Copy link
Author

thanks @neomerx. it is helpful to me to learn how to cope with the code and flow.
i will go and checkout your update, hope to catch them up very soon.

@dreamsbond
Copy link
Author

dreamsbond commented Sep 21, 2018

oh sorry, i didn't read your message properly. i just thought your update was there.
will be good if you don't mind sharing the codes in current stage so that i could try to catch and follow from scratch.

@neomerx
Copy link
Collaborator

neomerx commented Sep 21, 2018

Hi, I've added some code to the client and it seems to work (still very very beta). After you click Confirm you will be redirected back to client and the client will exchange the auth code to tokens

image

the code is here https://github.com/neomerx/demo-oauth-server

@neomerx
Copy link
Collaborator

neomerx commented Sep 21, 2018

The code has 2 commits

  • Initial commit (2 default Limoncello apps).
  • Server and client code added.

@dreamsbond
Copy link
Author

dreamsbond commented Sep 24, 2018

i am currently following your demo,

in the meantime, i looked into the method "setRedirectUriStrings()",
setRedirectUriStrings was obsolete, redirect uri can only be effective thru $this->seedClient

for you information

@neomerx
Copy link
Collaborator

neomerx commented Oct 2, 2018

September is over and I finally have a better schedule.

Back to setRedirectUriStrings this method is used to optimize reading clients. If you look at the database schema you will see that a client has relationships with other tables and data from those tables. Optimizations available for MySQL and PostgreSQL make it possible to read all data in one database query. Here you can see the usage of this method and here database migration that makes it possible.

@dreamsbond
Copy link
Author

besides the setRedirectUriStrings that i am look on.

I got problems redirecting to login page where user were not logged in.

here are the snippet:

                public function createAskResourceOwnerForApprovalResponse(
                    string $type,
                    ClientInterface $client,
                    string $redirectUri = null,
                    bool $isScopeModified = false,
                    array $scopeList = null,
                    string $state = null,
                    array $extraParameters = []
                ): ResponseInterface
                {
                    /** @var PassportAccountManagerInterface $manager */
                    $manager = $this->getContainer()->get(PassportAccountManagerInterface::class);
                    $passport = $manager->getPassport();
                    if ($passport === null) {
                        $signInUrl = static::createRouteUrl($this->getContainer(), AuthController::ROUTE_NAME_SIGN_IN);
                        return new RedirectResponse($signInUrl);
                    }

thru the methods in ControllerTrait;

I got an error there:

TypeError: Return value of Limoncello\Application\Http\RequestStorage::get() must implement interface Psr\Http\Message\ServerRequestInterface, null returned in D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\application\src\Http\RequestStorage.php:37
Stack trace:
#0 D:\github\dreamsbond\demo-oauth-server\server\server\app\Web\Controllers\ControllerTrait.php(418): Limoncello\Application\Http\RequestStorage->get()
#1 D:\github\dreamsbond\demo-oauth-server\server\server\app\Web\Controllers\ControllerTrait.php(400): class@anonymous::getHostUri(Object(Limoncello\Container\Container))
#2 D:\github\dreamsbond\demo-oauth-server\server\server\app\Container\OAuthConfigurator.php(57): class@anonymous::createRouteUrl(Object(Limoncello\Container\Container), 'auth_sign_in')
#3 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\passport\src\BasePassportServer.php(241): class@anonymous->createAskResourceOwnerForApprovalResponse('code', Object(Limoncello\Passport\Adaptors\Generic\Client), 'http://localhos...', true, Array, NULL, Array)
#4 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\oauth-server\src\GrantTraits\CodeGrantTrait.php(154): Limoncello\Passport\BasePassportServer->codeCreateAskResourceOwnerForApprovalResponse(Object(Limoncello\Passport\Adaptors\Generic\Client), 'http://localhos...', true, Array, NULL)
#5 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\passport\src\BasePassportServer.php(104): Limoncello\OAuthServer\BaseAuthorizationServer->codeAskResourceOwnerForApproval(Array, Object(Limoncello\Passport\Adaptors\Generic\Client), 'http://localhos...', NULL)
#6 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\oauth-server\src\BaseAuthorizationServer.php(77): Limoncello\Passport\BasePassportServer->createAuthorization(Array)
#7 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\passport\src\Package\PassportController.php(51): Limoncello\OAuthServer\BaseAuthorizationServer->getCreateAuthorization(Object(Zend\Diactoros\ServerRequest))
#8 [internal function]: Limoncello\Passport\Package\PassportController::authorize(Array, Object(Limoncello\Container\Container), Object(Zend\Diactoros\ServerRequest))
#9 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\core\src\Application\Application.php(339): call_user_func(Array, Array, Object(Limoncello\Container\Container), Object(Zend\Diactoros\ServerRequest))
#10 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\core\src\Application\Application.php(382): Limoncello\Core\Application\Application->callHandler(Array, Array, Object(Limoncello\Container\Container), Object(Zend\Diactoros\ServerRequest))
#11 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\passport\src\Authentication\PassportMiddleware.php(81): Limoncello\Core\Application\Application->Limoncello\Core\Application\{closure}(Object(Zend\Diactoros\ServerRequest))
#12 [internal function]: Limoncello\Passport\Authentication\PassportMiddleware::handle(Object(Zend\Diactoros\ServerRequest), Object(Closure), Object(Limoncello\Container\Container))
#13 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\core\src\Application\Application.php(493): call_user_func(Array, Object(Zend\Diactoros\ServerRequest), Object(Closure), Object(Limoncello\Container\Container))
#14 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\application\src\Packages\Session\SessionMiddleware.php(57): Limoncello\Core\Application\Application->Limoncello\Core\Application\{closure}(Object(Zend\Diactoros\ServerRequest))
#15 [internal function]: Limoncello\Application\Packages\Session\SessionMiddleware::handle(Object(Zend\Diactoros\ServerRequest), Object(Closure), Object(Limoncello\Container\Container))
#16 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\core\src\Application\Application.php(493): call_user_func(Array, Object(Zend\Diactoros\ServerRequest), Object(Closure), Object(Limoncello\Container\Container))
#17 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\application\src\Packages\Cors\CorsMiddleware.php(57): Limoncello\Core\Application\Application->Limoncello\Core\Application\{closure}(Object(Zend\Diactoros\ServerRequest))
#18 [internal function]: Limoncello\Application\Packages\Cors\CorsMiddleware::handle(Object(Zend\Diactoros\ServerRequest), Object(Closure), Object(Limoncello\Container\Container))
#19 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\core\src\Application\Application.php(493): call_user_func(Array, Object(Zend\Diactoros\ServerRequest), Object(Closure), Object(Limoncello\Container\Container))
#20 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\application\src\Packages\Cookies\CookieMiddleware.php(45): Limoncello\Core\Application\Application->Limoncello\Core\Application\{closure}(Object(Zend\Diactoros\ServerRequest))
#21 [internal function]: Limoncello\Application\Packages\Cookies\CookieMiddleware::handle(Object(Zend\Diactoros\ServerRequest), Object(Closure), Object(Limoncello\Container\Container))
#22 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\core\src\Application\Application.php(493): call_user_func(Array, Object(Zend\Diactoros\ServerRequest), Object(Closure), Object(Limoncello\Container\Container))
#23 D:\github\dreamsbond\demo-oauth-server\server\server\app\Web\Middleware\CookieAuth.php(51): Limoncello\Core\Application\Application->Limoncello\Core\Application\{closure}(Object(Zend\Diactoros\ServerRequest))
#24 [internal function]: App\Web\Middleware\CookieAuth::handle(Object(Zend\Diactoros\ServerRequest), Object(Closure), Object(Limoncello\Container\Container))
#25 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\core\src\Application\Application.php(493): call_user_func(Array, Object(Zend\Diactoros\ServerRequest), Object(Closure), Object(Limoncello\Container\Container))
#26 [internal function]: Limoncello\Core\Application\Application->Limoncello\Core\Application\{closure}(Object(Zend\Diactoros\ServerRequest))
#27 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\core\src\Application\Application.php(184): call_user_func(Object(Closure), Object(Zend\Diactoros\ServerRequest))
#28 D:\github\dreamsbond\demo-oauth-server\server\vendor\limoncello-php\core\src\Application\Application.php(141): Limoncello\Core\Application\Application->handleRequest(Object(Closure), Object(Zend\Diactoros\ServerRequest))
#29 D:\github\dreamsbond\demo-oauth-server\server\public\index.php(5): Limoncello\Core\Application\Application->run()
#30 {main}

I am going to make the redirect with a query parameter say "code_login=true", if the login checked code_login was present, it will pass to createAskResourceOwnerForApprovalResponse again for token exchange.

@dreamsbond
Copy link
Author

dreamsbond commented Oct 3, 2018

try out hardcoding uri

snippet:

                public function createAskResourceOwnerForApprovalResponse(
                    string $type,
                    ClientInterface $client,
                    string $redirectUri = null,
                    bool $isScopeModified = false,
                    array $scopeList = null,
                    string $state = null,
                    array $extraParameters = []
                ): ResponseInterface
                {
                    /** @var PassportAccountManagerInterface $manager */
                    $manager = $this->getContainer()->get(PassportAccountManagerInterface::class);
                    $passport = $manager->getPassport();
                    if ($passport === null) {
                        /** @var UriInterface $signInUri */
                        $signInUri = new Uri('http://localhost:8080/sign-in');
                        $signInWithQueryParamUri = $signInUri->withQuery(http_build_query([
                            'code_login' => true,
                        ]));
                        return new RedirectResponse($signInWithQueryParamUri);
//                        return new TextResponse('Not yet implemented. Sorry you have to log-in into server first and then authenticate from client.', 400);
                    }

and

    public static function authenticate(
        /** @noinspection PhpUnusedParameterInspection */
        array $routeParams,
        ContainerInterface $container,
        ServerRequestInterface $request
    ): ResponseInterface
    {
        $inputs = $request->getParsedBody();
        $codeLogin = $request->getQueryParams()['code_login'] == 1;

        if (is_array($inputs) === false) {
            return new HtmlResponse(static::view($container, Views::SIGN_IN_PAGE, [
                'error_message'       => 'Invalid input data.',
                'password_min_length' => User::MIN_PASSWORD_LENGTH,
            ]), 422);
        }

        // validate inputs
        $formValidator = static::createFormValidator($container, SignIn::class);
        if ($formValidator->validate($inputs) === false) {
            /** @var Traversable $errorMessages */
            $errorMessages = $formValidator->getMessages();
            $errorMessages = iterator_to_array($errorMessages);

            return new HtmlResponse(static::view($container, Views::SIGN_IN_PAGE, [
                'errors'              => $errorMessages,
                'previous'            => $inputs,
                'password_min_length' => User::MIN_PASSWORD_LENGTH,
            ]), 422);
        }
        $captures = $formValidator->getCaptures();
        list (self::FORM_EMAIL => $email, self::FORM_PASSWORD => $password) = $captures;
        $isRemember = $captures[static::FORM_REMEMBER] ?? false;
        assert(is_bool($isRemember));

        // actual check for user email and password
        /** @var PassportServerIntegrationInterface $passport */
        $passport = $container->get(PassportServerIntegrationInterface::class);
        $userId = $passport->validateUserId($email, $password);
        if ($userId === null) {
            return new HtmlResponse(static::view($container, Views::SIGN_IN_PAGE, [
                'error_message'       => 'Invalid email or password.',
                'previous'            => $inputs,
                'password_min_length' => User::MIN_PASSWORD_LENGTH,
            ]), 401);
        }

        // if we are here name and password are valid.
        // we have to create an auth token and return its value as a cookie.

        return static::authenticateUserById(
            $userId,
            $isRemember,
            $request->getQueryParams(),
            static::getSettings($container, Authorization::class),
//            static::createRouteUrl($container, HomeController::ROUTE_NAME_HOME),
            $codeLogin === true ? 'http://localhost:8080/authorize?response_type=code&client_id=client1' : static::createRouteUrl($container, HomeController::ROUTE_NAME_HOME),
            $passport,
            $container->get(CookieJarInterface::class)
        );
    }

works as expected but ugly to get redirection...

@dreamsbond
Copy link
Author

dreamsbond commented Oct 3, 2018

another question in mind.

the authorization code granted will be permanent or temporary?

@neomerx
Copy link
Collaborator

neomerx commented Oct 3, 2018

By default grant would be for 3600 seconds, then it should be prolonged with the refresh token.

The issue with making redirect URL is that you try to use ControllerTrait not from a controller but from middleware.

To remind you that a request goes through

app start -> container configurator 1 -> ... -> container configurator N -> middleware 1 -> ... -> middleware N -> Controller

then response goes back through all middlwares.

Method createRouteUrl depends on method getHostUri which in its turns depends on RememberRequestMiddleware which has to be very last middleware before controller. So when you use createRouteUrl from Passport middleware it fails because the current request is not remembered yet.

So what you can do?

  1. Get server base url (e.g. https://www.example.com) either from $_SERVER or from settings like
/** @var CacheSettingsProviderInterface $provider */
$provider  = $this->getContainer()->get(CacheSettingsProviderInterface::class);
$originUri = $provider->getApplicationConfiguration()[ApplicationConfigurationInterface::KEY_APP_ORIGIN_URI];
  1. Make an URL for login page
/** @var RouterInterface $router */
$router    = $this->getContainer()->get(RouterInterface::class);
$signInUrl = $router->get($originUri, AuthController::ROUTE_NAME_SIGN_IN);
  1. Return redirect response.

Also you might want to make redirect back after authentication. If so you should add some parameter with return URL to sign-in redirect. Then update \App\Web\Controllers\AuthController::authenticate where you will read it and use as 5th parameter while calling authenticateUserById.

@dreamsbond
Copy link
Author

i have just updated my fork, followed your guideline and works.

i have another question. i am still curious about how limoncello(client) knows the user authenticated is eligible to access the protected resource? the fingerprint wasn't persist in limoncello(client). and roles or scopes of limoncello(client) may also varies to the oauth authentication server; limoncello (server)

@neomerx
Copy link
Collaborator

neomerx commented Oct 4, 2018

The client knows the scopes allowed for that user. Having that knowledge client's developer can program the app accordingly.

The knowledge about the scope may come from places

  • any client has a default scope set
  • when a client gets token from the server it may have scope list (in case it was changed by the user and do not match the default set)

@dreamsbond
Copy link
Author

but on limoncello (client), i can see the validateScope is doing for scopes checking. but i checked that the oauth_token table behind do not have entry at all.

@neomerx
Copy link
Collaborator

neomerx commented Oct 4, 2018

validateScope works with own scopes (client's which are not used) but you use auth/scopes of the server.

Since you use a server to auth, issue tokens, provide API and etc, the client only gets a token and use the server. The server itself check scopes, permissions and etc.

When a client requests a token it knows it asks for permissions to do actions A, B, C and etc. The developer of the client knows which API methods will be allowed if A granted, same for B and C. Depending on actual granted scopes (think of them as allowed actions which may map to just 1 method (e.g. CREATE_POST) or a group of methods (e.g. MANAGE_CONTENT for CRUD for posts and comments)).

@dreamsbond
Copy link
Author

I see. So the code grant approval achieved the delegation to access the protected resources in limoncello (server).

How would this associate with the limoncello (client) to access its local protected resource?

@neomerx
Copy link
Collaborator

neomerx commented Oct 6, 2018

You can use the same approach we used in google auth. You can get from the server 'verified' email of the current user and then use the email to sign-in the user locally on the client so all security features of the client will be available for you.

@dreamsbond
Copy link
Author

let me try it out.

@dreamsbond
Copy link
Author

Everything goes good except the part I do for accessing local resource. Is there any google/apiclient alike library which is not point to google?

@neomerx
Copy link
Collaborator

neomerx commented Oct 31, 2018

the part I do for accessing local resource

What do you mean by that?

@dreamsbond
Copy link
Author

You mentioned about
You can use the same approach we used in google auth. You can get from the server 'verified' email of the current user and then use the email to sign-in the user locally on the client so all security features of the client will be available for you.

In case i am using google oauth, it is fine to use the library "google/apiclient". How about using the limoncello (client)?

@neomerx
Copy link
Collaborator

neomerx commented Nov 1, 2018

I meant you can receive user's email from limoncello server (either with the token or from a separate request) and then use this email to issue a local token.

We did that with Google. When we got the user's email from Google it was used to check local Limoncello database and issue our own token for the user.

@dreamsbond
Copy link
Author

will it risky though?

@dreamsbond
Copy link
Author

The payload was verified by the google/apiclient,

    /** Controller handler */
    const CALLABLE_GOOGLE_TOKEN_SIGN_IN = [self::class, 'googleTokenSignIn'];

    public static function googleTokenSignIn(
        /** @noinspection PhpUnusedParameterInspection */
        array $routeParams,
        ContainerInterface $container,
        ServerRequestInterface $request
    ): ResponseInterface {
        // you really **should** move it to settings
        // it's hardcoded for simplicity and keeping focused on google authentication
        $clientId = 'PUT-YOUR-GOOGLE-CLIENT-ID-HERE.apps.googleusercontent.com';

        // request information about the user with the token
        $idToken = $request->getParsedBody()['id_token'] ?? null;
        if ($idToken !== null) {
            $client = new \Google_Client(['client_id' => $clientId]);
            $payload = $client->verifyIdToken($idToken);
        } else {
            $payload = false;
        }

        if ($payload !== false && (bool)$payload['email_verified'] === true) {
            $userEmail = $payload['email'];

            /** @var \App\Api\UsersApi $api */
            $api    = static::createApi($container, \App\Api\UsersApi::class);
            $userId = $api->noAuthReadUserIdByEmail($userEmail);
            if ($userId !== null) {
                $shouldAuthCookie = true;

                return static::authenticateUserById(
                    $userId,
                    $shouldAuthCookie,
                    $request->getQueryParams(),
                    static::getSettings($container, Authorization::class),
                    static::createRouteUrl($container, HomeController::ROUTE_NAME_HOME),
                    $container->get(PassportServerIntegrationInterface::class),
                    $container->get(CookieJarInterface::class)
                );
            }
        }

        // If we get here it's either invalid ID token of some other auth issue (e.g. no user with email from Google)
        return new RedirectResponse(static::createRouteUrl($container, static::ROUTE_NAME_SIGN_IN));
    }

If in case i am not using google/apiclient, there will be a missing step on verifying the token and further to email verification. Am i correct?

@neomerx
Copy link
Collaborator

neomerx commented Nov 7, 2018

Of course, Google client works only with Google.

@neomerx
Copy link
Collaborator

neomerx commented Nov 7, 2018

This is callback method which will be called by Limoncello server. You will receive a token and any optional data. On the server side, you can add to the token any data such as email, name, phone number and whatever you wish. So getting user email might look like

$userEmail = json_decode((string)$request)->user_email;

@neomerx
Copy link
Collaborator

neomerx commented Nov 7, 2018

This method adds custom properties to a token

@dreamsbond
Copy link
Author

that is what i am confused. how could the callbacks knows the request was being verified if i am not using google/apiclient to verify.

@dreamsbond
Copy link
Author

the google/apiclient did a check

            $client = new \Google_Client(['client_id' => $clientId]);
            $payload = $client->verifyIdToken($idToken);

@neomerx
Copy link
Collaborator

neomerx commented Nov 7, 2018

I might need to create an example for that...

@dreamsbond
Copy link
Author

i mean how callback know it is a valid request and not someone pretended to be a valid request

@dreamsbond
Copy link
Author

Could u provide an example for limoncello app to limoncello app authorization code grant usage?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants