We have the need to login user ONLY via OpenId (exactly Microsoft Azure AD OpenId Connect).
We understand how to use Socialite but we would like to integrate with Backpack For Laravel, because app is 90% base cruds and because we already have a paid licence.
How to integrate socialite with backpack for laravel?
Also ... we should integrate it with laravel-permissions that it's very easy to integrate with backpack for laravel
If you need socialite, I recommend not using the Backpack authentication at all. Just disable everything related to authentication in your config/backpack/base.php file (the routes mainly), code your own controller logic with socialite and plug in your own auth middleware and guard within that same config file, so that Backpack uses your auth instead of the default one.
It will be easier to code and maintain your own logic, rather than mangling the default auth to work the way you want it.
This is actually a working solution
config\backpack\base.php
'guard' => null,
.env
AZURE_CLIENT_ID=0e8b592f-asaaaasd4eac-a368-d0d52dbc14e0
AZURE_CLIENT_SECRET=b2r5442
AZURE_REDIRECT_URI=/sso/callback
config\services.php
// See https://socialiteproviders.com/Microsoft-Azure/#installation-basic-usage
'azure' => [
'client_id' => env('AZURE_CLIENT_ID'),
'client_secret' => env('AZURE_CLIENT_SECRET'),
'redirect' => env('AZURE_REDIRECT_URI')
],
Packages installed:
"laravel/socialite": "^5.2",
"lcobucci/jwt": "^4.1",
"socialiteproviders/microsoft-azure": "^4.2",
routes\web.php
Route::get('/login', [\App\Http\Controllers\AuthController::class, 'login'])->name('login');
Route::get('/sso/callback', [\App\Http\Controllers\AuthController::class, 'ssoCallback']);
app/Http/Controllers/AuthController.php
use Laravel\Socialite\Facades\Socialite;
use Lcobucci\JWT\Configuration;
....
public function login()
{
return Socialite::driver('azure')->scopes(['openid'])->redirect();
}
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect("https://login.microsoftonline.com/common/oauth2/v2.0/logout");
}
public function ssoCallback()
{
try {
$user = Socialite::driver('azure')->user();
}
catch (\Laravel\Socialite\Two\InvalidStateException $e) {
return redirect(route('login'));
}
catch (\GuzzleHttp\Exception\ClientException $e) {
return redirect(route('login'));
}
// Read the claims from token JWT using Lcobucci\JWT package
$configuration = Configuration::forUnsecuredSigner();
$token = $configuration->parser()->parse( $user->accessTokenResponseBody["id_token"] );
$claims = $token->claims()
// This is an example, it depends by your jwt
$full_name = $user->name;
$email = $user->email;
$app_user = User::firstOrCreate([
'name' => $full_name,
'email' => $email,
]);
auth()->login($app_user);
}
Related
I’ve two Laravel based projects and I want to login with API which the first project provides and use this authentication in the second project.
in the LoginController in second project:
public function login(Request $request)
{
$login_response = Http::post('{first_project_login_api}', [
'data' => [
"email" => $request->input('email'),
"password" => $request->input('password')
]
]);
if ($this->attemptLogin($login_response)) {
return $this->sendLoginResponse($request);
}
}
protected function attemptLogin(Response $response)
{
return $response->object()->status === 200;
}
In the second project, I don't need to database because I want to authentication in the first project but does not seems to be possible!
actually I need to know how to overwrite attemptLogin() function in LoginController.
It would be highly appreciated if anyone can advise me!😊
Instead of using login between application, i would use API keys. The easiest way to get started is to use simple API Authentication.
First create migrations for the user table.
Schema::table('users', function ($table) {
$table->string('api_token', 80)->after('password')
->unique()
->nullable()
->default(null);
});
To get keys, set them on the user either in Tinker, command or creation.
$user->api_token = Str::random(60);
$user->save();
Protect your API routes with a middleware.
Route::middleware('auth:api')->group(function() {
// your routes
});
Calling your api is as simply as.
response = $client->request('POST', $yourRoute, [
'headers' => [
'Authorization' => 'Bearer ' . $yourToken,
'Accept' => 'application/json',
],
]);
This is a fairly basic setup, for production or moving forward you should look into Sanctum or Passport. This is just a good start, from where i feel you are based on your question.
We have 2 auth middlewares applied to specific routes, 'external_token' and 'auth:api'. When an external bearer token is presented we inspect it, and if all the values are good we consider the user authorized to access the requested url.
How do we process all other middlewares except passport auth?
public function handle(Request $request, Closure $next)
{
$token = $request->header('Bearer');
try {
list($JWTHeader, $JWTPayload) = JWT::verify($token, JWT::TYPE_ID_EXTERNAL);
$this->user = User::where('external_id', $JWTPayload['external_id'])->first();
// Can we just set $this->user and process all other middlewares except auth?
} catch (Exception $e) {
Log::debug($e);
}
$response = $next($request);
return $response;
}
Well, one thing you could do would be to set the user on the api guard, so when the auth middleware runs, it'll find the user you provided. You would have to ensure that your external_token middleware runs first.
auth()->guard('api')->setUser($this->user);
Another option would be to convert your external_token middleware into a Laravel auth guard so that you can use the built-in auth functionality. Then, you can protect your route with auth:api,external_token, and the auth will pass if any one of the specified guards is successful.
The simplest example would be a closure request guard.
In your AuthServiceProvider::boot() method:
// don't forget your "use" statements for all these classes
public function boot()
{
// ...
Auth::viaRequest('external_token_driver', function ($request) {
$token = $request->header('Bearer');
try {
list($JWTHeader, $JWTPayload) = JWT::verify($token, JWT::TYPE_ID_EXTERNAL);
return User::where('external_id', $JWTPayload['external_id'])->first();
} catch (Exception $e) {
Log::debug($e);
}
return null;
});
}
In your auth.php config:
'guards' => [
// web, api, etc...
'external_token' => [
'driver' => 'external_token_driver',
],
],
NB: all untested.
In Laravel 5.1 I used something like:
function enable(TokenRepositoryInterface $tokens)
{
// (...)
$token = $tokens->create($user);
Mail::send('emails.enabled', ['user' => $user, 'email' => $user->email, 'token' => $token], function ($m) use ($user) {
$m->to($user->email, $user->name)->subject('Welcome to ..., ' . $user->name . '!');
});
// (...)
}
to manually send a password-set token, after sign up (by abusing Laravels own password-reset tokens). However the TokenRepositoryInterface repository interface seems to be gone in Laravel 5.2.
Is there any way to manually create a password-reset token in Laravel 5.2 or do I need to manually implement that token handling for sign ups?
you can create a token using a passport in your laravel application
$user->createToken('Client Token')->accessToken;
I'd like to know is it possible to extend the built-in authentication to use an external API to authenticate a user? I'm a Laravel newbie, so I'd appreciate your help.
I'm making a custom app in Laravel 5.2 for my client, but I don't a direct access to their database server and I can only call their API to get users' details.
Thanks.
If I understood correctly you want to log users from APIs like facebook, twitter or github for example ? If that's so you need to use a laravel package named Socialite, here is the link to download and use it :
https://github.com/laravel/socialite
run on your command this :
composer require laravel/socialite
Next you need to tell laravel you want to use this package, so you need to add this in config/app.php :
'providers' => [
// Other service providers...
Laravel\Socialite\SocialiteServiceProvider::class,
],
and this is the aliases :
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
Basically, you'll need to create an app on the developers site, i'll take facebook for this example.You need to go to this site :
https://developers.facebook.com/, create an account and you'll get your app url and secret key. You'll use it on your .env and config/services files.
In your config/services file add this after stripe :
'facebook' => [
'client_id' => env('FACEBOOK_ID'),
'client_secret' => env('FACEBOOK_SECRET'),
'redirect' => env('FACEBOOK_URL'),
],
And in your .env file :
FACEBOOK_ID=*your facebook id*
FACEBOOK_SECRET=*your facebook secret*
FACEBOOK_URL=http://yourwebsite.com/callback
Next you'll need a controller to handle the auth process, create something like SocialAuthController and put this in :
public function redirect()
{
return Socialite::driver('facebook')->redirect();
}
public function callback() {
$user = $this->findOrCreateFbUser(Socialite::driver('facebook')->user());
session([
'user' => $user
]);
return redirect()->route('/');
}
public function logout() {
session()->forget('user');
return redirect()->route('home');
}
protected function findOrCreateFbUser($fbUser) {
// the data you want to get from facebook
$fbData = [
'facebook_id' => $fbUser->id,
'avatar' => $fbUser->avatar,
'username' => $fbUser->name,
'email' => $fbUser->email,
];
$user = \App\User::where('facebook_id', $fbData['facebook_id'])->first();
if(!$user) $user = \App\User::create($fbData);
$user->update([
'avatar' => $fbUser->avatar,
'username' => $fbUser->name,
'email' => $fbUser->email
]);
return $user;
}
Of course you need to add a facebook_id field in your user database and model.
In User.php :
protected $fillable = [
'facebook_id',
'username',
'email',
'avatar'
];
I know this solution isn't really dynamic as it is for only one api, i'm still pretty new at Laravel too and this is my first answer to a stackoverflowquestion, but this did the trick for me :) If I forgot something don't hesitate to tell me so i can update this answer..
I also suggest you follow Jeffrey Way's tutorial on social auth on the Laracasts website, it's very instructive and clear, i could manage it thanks to him !
I'm using Laravel 5.0 and trying to Authorize with Dropbox. I'm loosely following this example: http://dropbox.github.io/dropbox-sdk-php/api-docs/v1.1.x/class-Dropbox.WebAuth.html
When I go to /start. I get redirected to Dropbox and click "Allow", but when I get redirected back to /finish, I keep getting Missing CSRF token in session. Does anyone have any ideas? I have read that $_SESSION doesn't work in Laravel but I'm not sure how else to go about it.
Here is the code I am working with:
public function start()
{
$authorizeUrl = $this->getWebAuth()->start();
return redirect()->away($authorizeUrl);
}
public function finish()
{
$test = $this->getWebAuth()->finish($_GET);
dd($test);
}
private function getWebAuth()
{
$appKey = 'key';
$appSecret = 'secret';
$appName = 'name';
$appRedirect = 'http://example.com/finish';
$appInfo = new Dropbox\AppInfo($appKey, $appSecret);
$csrfTokenStore = new Dropbox\ArrayEntryStore($_SESSION, 'dropbox-auth-csrf-token');
$webAuth = new Dropbox\WebAuth($appInfo, $appName, $appRedirect, $csrfTokenStore);
return $webAuth;
}
Update 1:
Okay so I tried getting it working with Laravel Socialite and the Dropbox Socialite provider. I changed my code to what is below, but I get an error when I hit /start. Driver [dropbox] not supported. I got really confused on step 3 of the instructions, so maybe I did something wrong there.
composer.json
"require": {
"laravel/framework": "5.0.*",
"dropbox/dropbox-sdk": "1.1.*",
"laravel/socialite": "~2.0",
"socialiteproviders/dropbox": "~1.0"
},
Controller
use Socialite;
class ExampleController extends Controller {
public function start()
{
return Socialite::with('dropbox')->redirect();
}
public function finish()
{
$user = Socialite::with('dropbox')->user();
dd($user->token);
}
}
config/app.php
'providers' => [
//'Laravel\Socialite\SocialiteServiceProvider',
'SocialiteProviders\Manager\ServiceProvider',
],
'aliases' => [
'Socialite' => 'Laravel\Socialite\Facades\Socialite',
],
app/Providers/EventServiceProvider.php
protected $listen = [
'SocialiteProviders\Manager\SocialiteWasCalled' => [],
];
Update 2:
I figured it out, I added this and it worked.
app/Providers/EventServiceProvider.php
protected $listen = [
'SocialiteProviders\Manager\SocialiteWasCalled' => [
'SocialiteProviders\Dropbox\DropboxExtendSocialite#handle',
],
];
Why reinvent the wheel, if you have a wrapper that can do this for you:
https://github.com/GrahamCampbell/Laravel-Dropbox
The reason is that the POST routes are protected with CSRF. If you do not want to use a wrapper, you need to disable this security layer, but nobody would recommend that.
Even better is using Laravel Socialite. Only the fact is here that Dropbox is not natively supported in it, but this package will solve that.
Credits to ceejayoz for helping with this!
Note: Using a Dropbox package as in #Blaatpraat's answer is generally a better idea than this. If you're dead-set on using your own logic, though:
Laravel 5 POST routes (Dropbox is posting back to you at the end of the process) are protected by default by the CSRF protection middleware. Because Dropbox doesn't know your Laravel app's CSRF token (nor does it know to send one), the _token parameter is missing and fails the middleware.
You'll need to modify app/Http/Middleware/VerifyCsrfToken.php to exempt this route. Where it says:
return parent::handle($request, $next);
You'll want something like this to bypass the CSRF check on certain routes:
if(\Request::is('finish') { return $next($request); }
return parent::handle($request, $next);