Skip to content

Commit 5d38cc2

Browse files
rennokkialexatnewtontaylorotwell
authored
[9.x] User authentication for Pusher (#42531)
* Added /broadcasting/user-auth * Added typehint * csfixing * formatting * change wording * formatting Co-authored-by: Alex Renoki <[email protected]> Co-authored-by: Taylor Otwell <[email protected]>
1 parent ee26981 commit 5d38cc2

File tree

7 files changed

+148
-2
lines changed

7 files changed

+148
-2
lines changed

src/Illuminate/Broadcasting/BroadcastController.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Http\Request;
66
use Illuminate\Routing\Controller;
77
use Illuminate\Support\Facades\Broadcast;
8+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
89

910
class BroadcastController extends Controller
1011
{
@@ -22,4 +23,22 @@ public function authenticate(Request $request)
2223

2324
return Broadcast::auth($request);
2425
}
26+
27+
/**
28+
* Authenticate the current user.
29+
*
30+
* See: https://pusher.com/docs/channels/server_api/authenticating-users/#user-authentication.
31+
*
32+
* @param \Illuminate\Http\Request $request
33+
* @return \Illuminate\Http\Response
34+
*/
35+
public function authenticateUser(Request $request)
36+
{
37+
if ($request->hasSession()) {
38+
$request->session()->reflash();
39+
}
40+
41+
return Broadcast::resolveAuthenticatedUser($request)
42+
?? throw new AccessDeniedHttpException;
43+
}
2544
}

src/Illuminate/Broadcasting/BroadcastManager.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ public function routes(array $attributes = null)
7474
['get', 'post'], '/broadcasting/auth',
7575
'\\'.BroadcastController::class.'@authenticate'
7676
)->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
77+
78+
$router->match(
79+
['get', 'post'], '/broadcasting/user-auth',
80+
'\\'.BroadcastController::class.'@authenticateUser'
81+
)->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
7782
});
7883
}
7984

src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\Broadcasting\Broadcasters;
44

5+
use Closure;
56
use Exception;
67
use Illuminate\Container\Container;
78
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
@@ -17,6 +18,13 @@
1718

1819
abstract class Broadcaster implements BroadcasterContract
1920
{
21+
/**
22+
* The callback to resolve the authenticated user information.
23+
*
24+
* @var \Closure|null
25+
*/
26+
protected $authenticatedUserCallback = null;
27+
2028
/**
2129
* The registered channel authenticators.
2230
*
@@ -38,6 +46,34 @@ abstract class Broadcaster implements BroadcasterContract
3846
*/
3947
protected $bindingRegistrar;
4048

49+
/**
50+
* Resolve the authenticated user payload for the incoming connection request.
51+
*
52+
* See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication.
53+
*
54+
* @param \Illuminate\Http\Request $request
55+
* @return array|null
56+
*/
57+
public function resolveAuthenticatedUser($request)
58+
{
59+
if ($this->authenticatedUserCallback) {
60+
return $this->authenticatedUserCallback->__invoke($request);
61+
}
62+
}
63+
64+
/**
65+
* Register the user retrieval callback used to authenticate connections.
66+
*
67+
* See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication.
68+
*
69+
* @param \Closure $callback
70+
* @return void
71+
*/
72+
public function resolveAuthenticatedUserUsing(Closure $callback)
73+
{
74+
$this->authenticatedUserCallback = $callback;
75+
}
76+
4177
/**
4278
* Register a channel authenticator.
4379
*

src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,35 @@ public function __construct(Pusher $pusher)
3131
$this->pusher = $pusher;
3232
}
3333

34+
/**
35+
* Resolve the authenticated user payload for an incoming connection request.
36+
*
37+
* See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication
38+
* See: https://pusher.com/docs/channels/server_api/authenticating-users/#response
39+
*
40+
* @param \Illuminate\Http\Request $request
41+
* @return array|null
42+
*/
43+
public function resolveAuthenticatedUser($request)
44+
{
45+
if (! $user = parent::resolveAuthenticatedUser($request)) {
46+
return;
47+
}
48+
49+
$settings = $this->pusher->getSettings();
50+
$encodedUser = json_encode($user);
51+
$decodedString = "{$request->socket_id}::user::{$encodedUser}";
52+
53+
$auth = $settings['auth_key'].':'.hash_hmac(
54+
'sha256', $decodedString, $settings['secret']
55+
);
56+
57+
return [
58+
'auth' => $auth,
59+
'user_data' => $encodedUser,
60+
];
61+
}
62+
3463
/**
3564
* Authenticate the incoming request for a given channel.
3665
*

src/Illuminate/Support/Facades/Broadcast.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66

77
/**
88
* @method static \Illuminate\Broadcasting\Broadcasters\Broadcaster channel(string $channel, callable|string $callback, array $options = [])
9-
* @method static mixed auth(\Illuminate\Http\Request $request)
9+
* @method static \Illuminate\Broadcasting\BroadcastManager socket($request = null)
1010
* @method static \Illuminate\Contracts\Broadcasting\Broadcaster connection($name = null);
11+
* @method static mixed auth(\Illuminate\Http\Request $request)
12+
* @method static void resolveAuthenticatedUserUsing(Closure $callback)
1113
* @method static void routes(array $attributes = null)
12-
* @method static \Illuminate\Broadcasting\BroadcastManager socket($request = null)
1314
*
1415
* @see \Illuminate\Contracts\Broadcasting\Factory
1516
*/

tests/Broadcasting/BroadcasterTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Illuminate\Http\Request;
1111
use Mockery as m;
1212
use PHPUnit\Framework\TestCase;
13+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
1314
use Symfony\Component\HttpKernel\Exception\HttpException;
1415

1516
class BroadcasterTest extends TestCase
@@ -284,6 +285,36 @@ public function testRetrieveUserDontUseDefaultGuardWhenMultipleGuardsSpecified()
284285
$this->broadcaster->retrieveUser($request, 'somechannel');
285286
}
286287

288+
public function testUserAuthenticationWithValidUser()
289+
{
290+
$this->broadcaster->resolveAuthenticatedUserUsing(function ($request) {
291+
return ['id' => '12345', 'socket' => $request->socket_id];
292+
});
293+
294+
$user = $this->broadcaster->resolveAuthenticatedUser(new Request(['socket_id' => '1234.1234']));
295+
296+
$this->assertSame([
297+
'id' => '12345',
298+
'socket' => '1234.1234',
299+
], $user);
300+
}
301+
302+
public function testUserAuthenticationWithInvalidUser()
303+
{
304+
$this->broadcaster->resolveAuthenticatedUserUsing(function ($request) {
305+
return null;
306+
});
307+
308+
$user = $this->broadcaster->resolveAuthenticatedUser(new Request(['socket_id' => '1234.1234']));
309+
310+
$this->assertNull($user);
311+
}
312+
313+
public function testUserAuthenticationWithoutResolve()
314+
{
315+
$this->assertNull($this->broadcaster->resolveAuthenticatedUser(new Request(['socket_id' => '1234.1234'])));
316+
}
317+
287318
/**
288319
* @dataProvider channelNameMatchPatternProvider
289320
*/

tests/Broadcasting/PusherBroadcasterTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,31 @@ public function testValidAuthenticationResponseCallPusherPresenceAuthMethodWithP
146146
);
147147
}
148148

149+
public function testUserAuthenticationForPusher()
150+
{
151+
$this->pusher
152+
->shouldReceive('getSettings')
153+
->andReturn([
154+
'auth_key' => '278d425bdf160c739803',
155+
'secret' => '7ad3773142a6692b25b8',
156+
]);
157+
158+
$this->broadcaster = new PusherBroadcaster($this->pusher);
159+
160+
$this->broadcaster->resolveAuthenticatedUserUsing(function () {
161+
return ['id' => '12345'];
162+
});
163+
164+
$response = $this->broadcaster->resolveAuthenticatedUser(new Request(['socket_id' => '1234.1234']));
165+
166+
// The result is hard-coded from the Pusher docs
167+
// See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication
168+
$this->assertSame([
169+
'auth' => '278d425bdf160c739803:4708d583dada6a56435fb8bc611c77c359a31eebde13337c16ab43aa6de336ba',
170+
'user_data' => json_encode(['id' => '12345']),
171+
], $response);
172+
}
173+
149174
/**
150175
* @param string $channel
151176
* @return \Illuminate\Http\Request

0 commit comments

Comments
 (0)