There are many similar topics, but no solution is correct.
I need to be able to "authenticate" logged in users and guests for a specific presence channel only. How can I achieve this?
The rest of the channels are to be available as standard only to logged in users.
Came up with this the other day and it's just for a game so make up your own mind, but it looks okay to me. Maybe you can give me some security feedback ;D My situation is using token based auth with laravel sanctum.
If you want to use the presence channel, you need to have a user object for the guests too.
Broadcast::channel('{userRoom}', function ($user) {
return ['id' => $user->id, 'name' => $user->name];
});
My solution was having a guest model and sql table in addition to the regular users table for fully authed users. In my case, both user and guest models have a room property, a string, the users can create a room, and guests can join that room.
Have a seperate end point for guest authentication. I gathered a name, device name, and room, because for a game it made sense, but the point is that it was passwordless. The route returns a guest object and a bearer token that the guest can use to 'authenticate' themselves.
In config/auth.php add the new model to user providers:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'guests' => [
'driver' => 'eloquent',
'model' => App\Models\Guest::class,
]
],
Then just make sure your guests are supplying the bearer token when they set up their pusher instance. Javascript would be something like:
var pusher = new Pusher('xxxxxxxxxxxxxx', {
cluster: 'ap4',
authEndpoint: "https://example.com/broadcasting/auth",
auth: {
headers: {
Authorization: 'Bearer ' + token
}
}
})
var channel = pusher.subscribe('presence-xxxx');
channel.bind("pusher:subscription_succeeded", function () {
console.log("Subscription succeeded")
console.log(channel.members.me)
...
I guess just make sure you aren't giving the guests access to stuff they aren't supposed to get into. It's basically like having user roles.
Related
I am trying to figure out to provide multiple ways of authentication for the API service within my Laravel app. The app is a SPA using Vue.js and uses the API route to render and present all the view components. Currently, I am using a JWT driver for the API guard within the application. However, I'd also like to offer my clients the ability to access the same API via OAuth and Laravel's personal API token. With that being said, how do I protect my resources with the Auth middleware where it can be accessed internally with a JWT or externally by a client with OAuth or an API Token.
Controller:
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
// Make sure user is authenticated
$this->middleware('auth:api');
//$this->middleware('auth:oauth');
}
Auth Guards:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
'oauth' => [
'driver' => 'token',
'provider' => 'users',
]
],
If you want to be able allow multiple guards for your routes you can supply the different guards to the middleware call, like you have done already with the api guard, except you supply them as comma separated values:
$this->middleware('auth:api,oauth,web');
This will mean that if a user has been authenticated with one of the guards they will be able to access the route(s).
I want to let my users to login with different credentials in the same browser window, which is using the single users table. If tables were different then I will surely do that with guards, but the problem is I have to manage the user logins through single table.
Please help me how to manage multiple sessions in the same browser window, as when I login with other account in a new tab the first one goes logout.
Thanks in advance.
What I wanted to do was to maintain multiple session for a user, so he can log in with his other email-ids inside the same browser window in different tabs.
Here we go, how we can manage that and how Gmail is managing it.
At first you have to manage that, the user want to login with his other account or switch accounts. So you can show him the login page by appending any notation in url that shows he want to switch accounts.
If your original login URL is http://www.examle.com/login
then for multiple login, you can give him URL like http://www.examle.com/u/1/login (you can increase the number after u/ part as many times you want to switch accounts)
Then go to your config/sessions.php and edit your cookie part as follows
<?php
$user_type = ( ( !empty(request()) && (int)request()->segment(2) ) > 0 ? '_'. request()->segment(2) : '');
return [
//.....rest of array
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'. $user_type //This user_type generate various session keys for your multiple login according to generated URL
),
];
Then you have to change your all URL's as dynamic so that it can execute for both your normal route(without '/u/number/url' part) and with the /u/number/url part.
Define the following variable at the top of your web.php
/**
* Setting a variable to check if the user is logging in with first or multiple sessions
*/
$user_login = ( (int)request()->segment(2) > 0 ? 'u/'. request()->segment(2) : '' );
/**
* User attempting to login with other accounts
*/
Route::post($user_login. '/login', 'Auth\LoginController#login');
/**
* Get dashboard for filling the registeration forms
* Your entire app URL will now go like this, whether you can use it with user number or without it. It will go smoothly
*/
Route::get($user_login. '/dashboard', ['as' => 'dashboard', 'uses' => 'FormController#getDashboard']);
/**
* User attempting to login with other accounts
*/
Route::post($user_login. '/logout', 'Auth\LoginController#logout');
This works great. Thanks everyone for the help.
Create a new guard in admin auth with same model.
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'clients' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'front' => [
'driver' => 'session',
'provider' => 'clients',
],
In the controller:
if ($this->guard()->attempt(['email' => $request->email, 'password' => $request->password, 'active' => 1])) {
dd(' i am logged in');
}
}
protected function guard()
{
return auth()->guard('front');
}
I have an Laravel-base API which handles both client and admin endpoints (there are two sites like domain.com and admin.domain.com).
My auth is based on cookie, which domain is <.domain.com>. As you can see, this cookie is acceptable for both domains.
I use Eloquent Api Resources for transformation data layer. Is my when() route check here safe and right?
public function toArray($request)
{
return [
'name' => $this->name,
'created_at' => (string)$this->created_at,
'status' => $this->when($request->route()->getName() === 'api.admin.users.index', $this->status)
];
}
Before I used $this->when(Auth::check(), ...), but because my auth cookie is acceptable for client site too, unneeded data might be fetched.
My route:
Route::group(['prefix' => 'admin', 'as' => 'api.admin.', 'middleware' => 'auth:api'], function () {
Route::resource('users', ...);
});
If user is not authorized, he wouldn't get data because of middleware. At the same time, authorized used (who has non-expired cookie) wouldn't get unneded data while being on client site.
Thank you!
I think your approach is fine. The route name is something internal and the user cannot tinker with it. You could improve it by using \Route::is('api.admin.*') though. It would then work for all of your admin API routes.
I have a traditional web application that has a number of different user types, and each user type has its own Authentication guard.
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admin',
],
'timekeeper' => [
'driver' => 'session',
'provider' => 'timekeeper',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
Most my users authenticate using the 'web' guard, however administrators and timekeepers each use their own guard, which is attached to an appropriate user provider.
This is fine until I try to use authentication gates. If I authenticate a user against the system's default guard (e.g. 'web'), then the gates work as expected. If I authenticate against any other guard however, then all Gate::allows(...) calls are DENIED.
Even the following ability is denied:
Gate::define('read', function ($user) {
return true;
});
Presumably this is due to line 284-286 in Illuminate\Auth\Access\Gate:
if (! $user = $this->resolveUser()) {
return false;
}
As far as I can see, my options are to:
Go back to using a single 'web' guard, with a user provider that can locate any type of user (but I'm not sure how that would work if I start using an API in parallel)
Somehow set the default guard at run time, depending on the type of the current user. (It is currently set in the config file)
Somehow inject a different user resolver in to the Gate facade (again, depending on the type of the current user)
None of these seems intuitive however. Am I missing something?
It's not the most elegant solution because it requires a lot of extra boilerplate code, but you can use Gate::forUser($user)->allows() instead of just Gate::allows() where $user comes from Auth::guard().
I had the same problem and I didn't really like this solution. After quite a lot of research I came up with this way to make your own user resolver in the Gate:
public function register()
{
$this->app->singleton(GateContract::class, function ($app) {
return new \Illuminate\Auth\Access\Gate($app, function () use($app) {
$user = call_user_func($app['auth']->userResolver());
if (is_null($user)) {
// Implement your own logic for resolving the user
}
return $user;
});
});
}
I put this in my AuthServiceProvider.
Out of the gate, the auth config for Laravel specifies a token-based authentication approach for users:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
I have a few ajax endpoints I want to secure so no one outside of my application can interact with them. I've looked at Passport but it seems I may not actually need it given this auth configuration. How can I utilize this token to secure my ajax endpoints and if possible, identify the user the request belongs to?
Currently my api.php route file looks like:
//Route::middleware('auth:api')->group(function () {
Route::post('subscribe', 'SubscriptionController#create');
Route::post('unsubscribe', 'SubscriptionController#delete');
//});
I thought Laravel might've handled auth or something out of the gate for VueJS implementation but it doesn't look like it. My ajax request looks like:
this.$http.post('/api/subscribe', {
subscribed_by: currentUser,
game_id: this.gameId,
guild_discord_id: this.guildDiscordId,
channel_id: newChannelId,
interests: this.interests.split(',')
}).then(response => {
// success
}, response => {
console.error('Failed to subscribe');
});
As Maraboc already said, you should start by creating a column api_token: $table->string('api_token', 60)->unique(); in your users table.
Make sure each newly created user gets a token assigned, and encrypt it: $user->api_token = encrypt(str_random(60));
Next, you could define a Javascript variable in the footer of your app:
window.Laravel = <?php echo json_encode([
'apiToken' => !empty(Auth::user()) ? decrypt(Auth::user()->api_token) : ''
]); ?>;
Later, when you want to make a request to an endpoint, you should add a header, authorizing the user:
let url = '/path/to/your-endpoint.json';
let data = {
headers: {
'Authorization': 'Bearer ' + Laravel.apiToken
}
};
axios.get(url, data)
.then(response => console.dir(response));
Finally, in your controller, you can get your User instance by using Laravel's guard:
$user = !empty(Auth::guard('api')->user()) ? Auth::guard('api')->user() : null;
Hope this helps! BTW: these articles helped me on my way:
https://gistlog.co/JacobBennett/090369fbab0b31130b51
https://pineco.de/laravel-api-auth-with-tokens/
The solution I took was to not put ajax endpoints in the api namespace. By putting them as web routes instead of api it'll use CSRF (cross-site request forgery) protection to validate the route. So only if it comes from my domain will it be authenticated. This is ONLY useful when the site is served in https.