Reputation: 198
I need to solve the following problem: I created two applications using Laravel.
https://test.example.org
and provides a passport-instance and all user-data (Model: User
). https://test.org
and uses the API Application A provides using an "api-user" that exists in Application A to fetch "public" dataNow I would like to provide a login method for users of Application B. All users that are using Application B are existing in Application A. So a user logging in on Application B is using the credentials that are saved in Application A. All changes that are made to the user in Application A should be visible on Application B.
Now I wonder how I could do that. Any idea how to solve this? I can imagine using API-calls with
$client = $this->client;
try {
return $client->request('POST',
'https://'.config('server').'/oauth/token', [
'headers' => [
'cache-control' => 'no-cache',
'Content-Type' => 'application/x-www-form-urlencoded',
],
'form_params' => [
'client_id' => ...,
'client_secret' => ...,
'grant_type' => 'password',
'username' => $users-username,
'password' => $users-password,
],
]);
} catch (BadResponseException $e) {
return $e->getResponse();
}
}
but I don't get, whether I need to create a client_id
and client_secret
for each user on Application A. If this is the case, I'd need to store these information somewhere so I'd end up creating a User
model on Application B itself.
Upvotes: 4
Views: 3309
Reputation: 838
The most elegant way would be to use Laravel Socialite in your Application B to act as an OAuth client for Application A which acts as an OAuth server (using Laravel Passport).
php artisan passport:client --password
First, you will be asked Which user ID should the client be assigned to?
. Press enter as you don't want to restrict the client to a specific user.
Then, you will be asked to provide a name for your client, your should enter something like Application B
.
At last, you will be asked for the redirect URL of you client. You should enter https://test.org/oauth/callback
, which is the URL that Socialite will use to redirect the users back after they are authenticated.
If you need to test this on your machine, enter your local domain, e.g. https://application-b.test/oauth/callback
.
Once the client is created, keep the Client ID and Client secret as you will need them later.
You will also need an API endpoint to provide the details for the authenticated user, .e.g. https://test.example.org/user
.
services.php
configuration file. For example:'passport' => [
'client_id' => env('PASSPORT_CLIENT_ID'),
'client_secret' => env('PASSPORT_CLIENT_SECRET'),
'redirect' => 'oauth/callback',
],
In your .env
file, set the client ID and secret for the Passport client you created in Application A:
PASSPORT_CLIENT_ID=...
PASSPORT_CLIENT_SECRET=...
Application A
.Create a PassportProvider
class in the app/Socialite
directory (or anywhere inside app
if you prefer).
<?php
namespace App\Socialite;
use Laravel\Socialite\Two\AbstractProvider;
use Laravel\Socialite\Two\ProviderInterface;
use Laravel\Socialite\Two\User;
use Illuminate\Support\Arr;
class PassportProvider extends AbstractProvider implements ProviderInterface
{
/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://test.example.org/oauth/authorize', $state);
}
/**
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return 'https://test.example.org/oauth/token';
}
/**
* {@inheritdoc}
*/
protected function getUserByToken($token)
{
$response = $this->getHttpClient()->get(
'https://test.example.org/user',
$this->getRequestOptions($token)
);
return json_decode($response->getBody(), true);
}
/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user)
{
return (new User)->setRaw($user)->map([
'id' => $user['id'],
'name' => Arr::get($user, 'name'),
'email' => Arr::get($user, 'email'),
]);
}
/**
* Get the default options for an HTTP request.
*
* @param string $token
* @return array
*/
protected function getRequestOptions($token)
{
return [
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'token '.$token,
],
];
}
}
Next, in your ApplicationServiceProvider
(or in a separate service provider), add the following:
use App\Socialite\PassportProvider;
use Illuminate\Support\Facades\URL;
use Laravel\Socialite\Facades\Socialite;
public function boot()
{
Socialite::extend('passport', function (function ($app) {
$config = $app['config']['services.passport'];
return new PassportProvider(
$app['request'],
$config['client_id'],
$config['client_secret'],
URL::to($config['redirect'])
);
});
}
Now you are ready to use your custom Socialite provider in your login controller on your client application:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Laravel\Socialite\Facades\Socialite;
class LoginController extends Controller
{
/**
* Redirect the user to the GitHub authentication page.
*
* @return \Illuminate\Http\RedirectResponse
*/
public function redirectToProvider()
{
return Socialite::driver('passport')->redirect();
}
/**
* Obtain the user information from GitHub.
*
* @return \Illuminate\Http\Response
*/
public function handleProviderCallback()
{
$user = Socialite::driver('passport')->user();
}
}
Don't forget to register the login routes in your routes/web.php
file:
Route::get('oauth/redirect', 'Auth\LoginController@redirectToProvider');
Route::get('oauth/callback', 'Auth\LoginController@handleProviderCallback');
Upvotes: 9
Reputation: 141
there is two solutions
1. If you just need Application A database details than create multiple database connection in your Application B
https://fideloper.com/laravel-multiple-database-connections
2. If you want to access Application A endpoint than keep same files storage/oauth-public.key, storage/oauth-private.key for your both Application so Application A can easily Authenticate Application B authorisation token.
Upvotes: 0
Reputation: 1409
A. You would only create one client_id and client_secret for Application B
B. Use Passport as you wrote above
C. You can, but you need to set it up manually which I'm not sure is a good idea, a better one would be to redirect it to Application A UI (In case of web) or directly contact Application A (in case of mobile) and let it handle it by itself.
Upvotes: 0