Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ as well as the community driven [Socialite Providers](https://socialiteproviders

- Handle user registration via third party providers;
- Handle user log in via third party providers;
- Allow existing user to link a third party identity;
- Customizable controllers, migration and models that will live in your application namespace;
- Save identity and token inside the database, using
[encryption and pseudoanonimization](#how-data-is-stored-in-the-database);
- Provide login/register button as Blade component;
- Provide login/register/connect button as Blade component;
- Support all [Laravel Socialite](https://laravel.com/docs/socialite)
and [Socialite Providers](https://socialiteproviders.com/);
- Add custom providers.
Expand Down Expand Up @@ -130,7 +131,7 @@ however we provide a Blade Component to quickly add login and register links/but
class="button button--primary" />
```

The available `action`s are `login` and `register`. The `provider` refers to what
The available `action`s are `login`, `connect` and `register`. The `provider` refers to what
identity provider to use, the name of the provider is the same as the Socialite
providers' name. See [Blade components](https://laravel.com/docs/blade#components) for more.

Expand Down
17 changes: 2 additions & 15 deletions src/Auth/AuthenticatesUsersWithIdentity.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@

namespace Oneofftech\Identities\Auth;

use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\RedirectsUsers;
use Illuminate\Validation\ValidationException;
use Oneofftech\Identities\Facades\Identity;
use Oneofftech\Identities\Facades\IdentityCrypt;
use Oneofftech\Identities\Support\FindIdentity;
use Oneofftech\Identities\Support\InteractsWithPreviousUrl;

trait AuthenticatesUsersWithIdentity
{
use RedirectsUsers, InteractsWithPreviousUrl;
use RedirectsUsers, InteractsWithPreviousUrl, FindIdentity;

/**
* Redirect the user to the provider authentication page.
Expand Down Expand Up @@ -71,18 +70,6 @@ public function login(Request $request, $provider)
return $this->sendLoginResponse($request);
}

/**
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
protected function findUserFromIdentity($identity, $provider)
{
try {
return Identity::findUserByIdentity($provider, IdentityCrypt::hash($identity->getId()));
} catch (ModelNotFoundException $mntfex) {
return null;
}
}

/**
* Get the failed login response instance.
*
Expand Down
133 changes: 133 additions & 0 deletions src/Auth/ConnectUserIdentity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php

namespace Oneofftech\Identities\Auth;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\RedirectsUsers;
use Illuminate\Support\Facades\DB;
use Oneofftech\Identities\Facades\IdentityCrypt;
use Oneofftech\Identities\Facades\Identity;
use Oneofftech\Identities\Support\FindIdentity;
use Oneofftech\Identities\Support\InteractsWithPreviousUrl;
use Oneofftech\Identities\Support\InteractsWithAdditionalAttributes;

trait ConnectUserIdentity
{
use RedirectsUsers, InteractsWithPreviousUrl, InteractsWithAdditionalAttributes, FindIdentity;

/**
* Redirect the user to the Authentication provider authentication page.
*
* @return \Illuminate\Http\Response
*/
public function redirect(Request $request, $provider)
{
// save the previous url as the callback will
// probably have the referrer header set
// and in case of validation errors the
// referrer has precedence over _previous.url
$this->savePreviousUrl();

// get additional user defined attributes
$this->pushAttributes($request);

return Identity::driver($provider)
->redirectUrl(route('oneofftech::connect.callback', ['provider' => $provider]))
->redirect();
}

/**
* Obtain the user information from Authentication provider.
*
* if the identity exists it will be updated, otherwise a new identity will be created
*
* @return \Illuminate\Http\Response
*/
public function connect(Request $request, $provider)
{
// Load the previous url from the
// session to redirect back in
// case of errors
$previous_url = $this->getPreviousUrl();

$oauthUser = Identity::driver($provider)
->redirectUrl(route('oneofftech::connect.callback', ['provider' => $provider]))
->user();

// if user denies the authorization request we get
// GuzzleHttp\Exception\ClientException
// Client error: `POST https://gitlab.com/oauth/token` resulted in a `401 Unauthorized`
// response: {"error":"invalid_grant","error_description":"The provided authorization grant is invalid, expired, revoked, does not ma (truncated...)

// GuzzleHttp\Exception\ClientException
// Client error: `POST https://gitlab/oauth/token` resulted in a `401 Unauthorized`
// response: {"error":"invalid_grant","error_description":"The provided authorization grant is invalid, expired, revoked, does not ma (truncated...)

$user = $request->user();

// create or update the user's identity

list($user, $identity) = DB::transaction(function () use ($user, $provider, $oauthUser) {
$identity = $this->createIdentity($user, $provider, $oauthUser);

return [$user, $identity];
});

// todo: event(new Connected($user, $identity));

return $this->sendConnectionResponse($request, $identity);
}

protected function createIdentity($user, $provider, $oauthUser)
{
return $user->identities()->updateOrCreate(
[
'provider'=> $provider,
'provider_id'=> IdentityCrypt::hash($oauthUser->getId())
],
[
'token'=> IdentityCrypt::encryptString($oauthUser->token),
'refresh_token'=> IdentityCrypt::encryptString($oauthUser->refreshToken),
'expires_at'=> $oauthUser->expiresIn ? now()->addSeconds($oauthUser->expiresIn) : null,
'registration' => true,
]
);
}

protected function sendConnectionResponse(Request $request, $identity)
{
$request->session()->regenerate();

if ($response = $this->connected($this->guard()->user(), $identity, $this->pullAttributes($request), $request)) {
return $response;
}

return redirect()->intended($this->redirectPath());
}

/**
* The user identity has been connected.
*
* @param mixed $user
* @param mixed $identity
* @param array $attributes
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function connected($user, $identity, array $attributes, Request $request)
{
//
}

/**
* Get the guard to retrieve currently authenticated user.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard();
}
}
46 changes: 2 additions & 44 deletions src/Auth/RegistersUsersWithIdentity.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
use Laravel\Socialite\AbstractUser as SocialiteUser;
use Oneofftech\Identities\Facades\Identity;
use Oneofftech\Identities\Support\InteractsWithPreviousUrl;
use Oneofftech\Identities\Support\InteractsWithAdditionalAttributes;

trait RegistersUsersWithIdentity
{
use RedirectsUsers, InteractsWithPreviousUrl;
use RedirectsUsers, InteractsWithPreviousUrl, InteractsWithAdditionalAttributes;

/**
* Redirect the user to the Authentication provider authentication page.
Expand Down Expand Up @@ -163,47 +164,4 @@ protected function guard()
{
return Auth::guard();
}

/**
* The attributes that should be retrieved from
* the request to append to the redirect
*
* @var array
*/
protected function redirectAttributes()
{
if (method_exists($this, 'attributes')) {
return $this->attributes();
}

return property_exists($this, 'attributes') ? $this->attributes : [];
}

protected function pushAttributes($request)
{
$attributes = $this->redirectAttributes() ?? [];

if (empty($attributes)) {
return;
}

$request->session()->put('_oot.identities.attributes', json_encode($request->only($attributes)));
}

protected function pullAttributes($request)
{
$attributes = $this->redirectAttributes() ?? [];

if (empty($attributes)) {
return [];
}

$savedAttributes = $request->session()->pull('_oot.identities.attributes') ?? null;

if (! $savedAttributes) {
return [];
}

return json_decode($savedAttributes, true);
}
}
5 changes: 5 additions & 0 deletions src/Facades/Identity.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ public static function routes()
->name("oneofftech::register.provider");
$router->get('register-via/{provider}/callback', "$namespace\Http\Controllers\Identities\Auth\RegisterController@register")
->name("oneofftech::register.callback");

$router->get('connect-via/{provider}', "$namespace\Http\Controllers\Identities\Auth\ConnectController@redirect")
->name("oneofftech::connect.provider");
$router->get('connect-via/{provider}/callback', "$namespace\Http\Controllers\Identities\Auth\ConnectController@connect")
->name("oneofftech::connect.callback");
}

/**
Expand Down
22 changes: 22 additions & 0 deletions src/Support/FindIdentity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Oneofftech\Identities\Support;

use Oneofftech\Identities\Facades\Identity;
use Oneofftech\Identities\Facades\IdentityCrypt;
use Illuminate\Database\Eloquent\ModelNotFoundException;

trait FindIdentity
{
/**
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
protected function findUserFromIdentity($identity, $provider)
{
try {
return Identity::findUserByIdentity($provider, IdentityCrypt::hash($identity->getId()));
} catch (ModelNotFoundException $mntfex) {
return null;
}
}
}
50 changes: 50 additions & 0 deletions src/Support/InteractsWithAdditionalAttributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Oneofftech\Identities\Support;

trait InteractsWithAdditionalAttributes
{

/**
* The attributes that should be retrieved from
* the request to append to the redirect
*
* @var array
*/
protected function redirectAttributes()
{
if (method_exists($this, 'attributes')) {
return $this->attributes();
}

return property_exists($this, 'attributes') ? $this->attributes : [];
}

protected function pushAttributes($request)
{
$attributes = $this->redirectAttributes() ?? [];

if (empty($attributes)) {
return;
}

$request->session()->put('_oot.identities.attributes', json_encode($request->only($attributes)));
}

protected function pullAttributes($request)
{
$attributes = $this->redirectAttributes() ?? [];

if (empty($attributes)) {
return [];
}

$savedAttributes = $request->session()->pull('_oot.identities.attributes') ?? null;

if (! $savedAttributes) {
return [];
}

return json_decode($savedAttributes, true);
}
}
5 changes: 3 additions & 2 deletions src/View/Components/IdentityLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@

class IdentityLink extends Component
{
private static $availableActions = ['register', 'login'];
private static $availableActions = ['register', 'login', 'connect'];

private static $actionLabels = [
'register' => 'Register via :Provider',
'login' => 'Log in via :Provider'
'login' => 'Log in via :Provider',
'connect' => 'Connect :Provider',
];

/**
Expand Down
Loading