Trying to build and API based on laravel that aims to grow to intense usage by lots of clients. My question is whether there are serious drawbacks of using Auth class in my code ?
I have implemented the OAuth2 authorization, and to get info about the user that is making the request I have a filter :
Route::filter('hasAccess', function($request)
{
//get the cleaned token string
$auth_code = Request::header('Authorization');
$auth_code = trim(preg_replace('/Bearer/sui', "", $auth_code));
//get the stored session and put the query in cache for 10 minutes
$ts = DB::table('sessions as s')
->leftJoin('oauth_session_access_tokens as osat', 's.token', '=', 'osat.id')
->select('s.*')
->where('osat.access_token', '=', $auth_code)
->remember(10, $auth_code)
->first();
//Auth user cross-app
Auth::onceUsingId($ts->user);
//Extract the requested action
$request = $request->getAction();
$request = $request['controller'];
$parts = explode('#', $request);
$required = strtolower($parts[0]).'.'.$parts[1];
$required = preg_replace('/controller/sui', "", $required);
//Get the permissions
$permissions = json_decode($ts->permissions, true);
$permissions = array_fetch($permissions,'name');
if (!in_array($required,$permissions))
{
return Response::json([
'error' => true,
'dataset' => 'You don\'t have rights to access this url'
]);
}
});
It validates the user access rights to the controller action, but the most interesting in it is the row with Auth::onceUsingId($ts->user);. This rows authorizez the user for only 1 request. Also if any other ways to get info about user exist, please mention them. Thanks
You talk about 'serious drawbacks' of using Auth class code - but you dont really explain drawbacks compared to what? Just manually looking in the database yourself for the user?
All the Auth::onceUsingId() is doing is logging your user into the application without a session or cookie. This is perfect for an API - as you dont normally have persistence between requests.
You can then do Auth::user() to get data about the user, such as Auth::user()->name.
Related
Context
I am trying to make webservice that fetches the name and email from an users Apple account and place a Song or Artist in his library.
For adding a Song to the library I found this apple-music-api. library. To make requests on behalf of a user you need to request a user token with Apple MusicKit JS library.
For fetching the name and email of the user I use this oauth2 client that uses the signin with Apple functionality.
Problem
A Using the apple music kit... I can not query any user profile data. At least I cannot seem to find an example nor any documentation of this. Is there a possibility to get the user email and name using this route?
B Using the Sign in with Apple oauth flow I receive an access token which contains the name and email. But I cannot use the token to query the apple music api. It seems their scopes are limited to the name and email...and no scope for the music api or related seems to exist. Is there a possibility to get an user token that can be used on the music api?
C Are there any other possibilities to accomplish this without requiring the user to sign in twice on apple (once for the email and once for pushing the Song to his library)
What I tried for option B
// $leeway is needed for clock skew
Firebase\JWT\JWT::$leeway = 60;
$provider = new League\OAuth2\Client\Provider\Apple([
'clientId' => 'com.myapp.www',
'teamId' => 'team.id', // 1A234BFK46 https://developer.apple.com/account/#/membership/ (Team ID)
'keyFileId' => 'key.id', // 1ABC6523AA https://developer.apple.com/account/resources/authkeys/list (Key ID)
'keyFilePath' => dirname(__FILE__) . '/AuthKey_key.id.p8', // __DIR__ . '/AuthKey_1ABC6523AA.p8' -> Download key above
'redirectUri' => PLUGIN_URL . 'callback-apple-music.php',
]);
if (isset($_POST['code'])) {
if (empty($_POST['state']) || !isset($_COOKIE['apple-oauth2state']) || ($_POST['state'] !== $_SESSION['apple-oauth2state'])) {
unset($_COOKIE['apple-oauth2state']);
exit('Invalid state');
} else {
try {
// Try to get an access token (using the authorization code grant) via signin_with_apple
/** #var AppleAccessToken $token */
$token = $provider->getAccessToken('authorization_code', [
'code' => $_POST['code']
]);
$access_token = $token->getToken();
// create an client for api.music.apple
$tokenGenerator = new PouleR\AppleMusicAPI\AppleMusicAPITokenGenerator();
$jwtToken = $tokenGenerator->generateDeveloperToken(
'team.id',
'key.id',
dirname(__FILE__) .'/AuthKey_key.id.p8'
);
// create a developer token again
$curl = new \Symfony\Component\HttpClient\CurlHttpClient();
$client = new PouleR\AppleMusicAPI\APIClient($curl);
$client->setDeveloperToken($jwtToken);
$api = new PouleR\AppleMusicAPI\AppleMusicAPI($client);
$api->setMusicUserToken($access_token);
// This endpoint needs authorisation
$result = $api->getAllLibraryPlaylists(); //https://api.music.apple.com/v1/me/library/playlists?offset=0&limit=25
echo '<pre>';
print_r($result);
echo '</pre>';
// wp_redirect($redirect_url);
exit;
} catch (Exception $e) {
echo '<pre>';
print_r($e);
echo '</pre>';
}
}
}
The problem with the question is that these are three questions - and not telling which client.
Most commonly "login with" is only good for creating local accounts without much typing.
And it is quite likely intentional, that the oAuth2 scope is extremely limited for this purpose.
And I've looked it up ...one needs a "Music User Token":
https://developer.apple.com/documentation/applemusicapi/getting_keys_and_creating_tokens
And this token needs to be passed as HTTP header: 'Music-User-Token: [music user token]'.
Which means, that the user token may either originate from an iOS device (you'd need to expose eg. a REST API, so that it can be posted and then used by PHP as HTTP header, on the server-side): https://developer.apple.com/documentation/storekit/skcloudservicecontroller/2909079-requestusertoken (this only requires a login to your own API).
When running Apple MusicKit JS on the cient-side (browser), two logins may not be evitable:
https://developer.apple.com/documentation/musickitjs/musickit/musickitinstance/2992701-authorize
It makes no sense to use both of these flows within the same method(which also ignores the principle of single responsibility).
I am building a webapp that is supposed to be hosted in my company servers and used through the intranet. The requirements are:
The user accesses the webapp.
The app requests an e-mail address.
An e-mail containing a unique link (token) is sent to the address.
The user clicks on the link to log in without a password.
I am developing the webapp using Symfony3 and I thought of using the FriendsOfSymfony User bundle. How can I acheive that? FOSUserBundle is not mandatory.
The login functionalities you want to achieve do not diver that much from e.g. resetting a password by email. Except the temporary token in your use case is used to authenticate the user instead of authenticating a password reset.
A very simple explanation
You should create an entity that stores the authentication token, e.g. AutoLogin that has user, token and a timestamp property.
On the submit of your 'authentication form' a new AutoLogin record gets stored with a relationship towards the user and the user gets notified by email.
Whenever the user clicks the link you should have a method that validates the timestamp for a timeframe and authenticate the user by your user provider.
Examples
Symfony 2: AutoLogin
I think after you accepted the email this is what you can do:
sent url to email like this
<?php
$url = "http://example.com/login.php?token=$token";
?>
Then you login page
<?php
// retrieve token
if (isset($_GET["token"]) && preg_match('/^[0-9A-F]{40}$/i', $_GET["token"]))
{
$token = $_GET["token"];
}
else {
throw new Exception("Valid token not provided.");
}
// verify token
$query = $db->prepare("SELECT username, tstamp FROM pending_users WHERE token = ?");
$query->execute(array($token));
$row = $query->fetch(PDO::FETCH_ASSOC);
$query->closeCursor();
if ($row) {
extract($row);
}
else {
throw new Exception("Valid token not provided.");
}
// do action here, like activating a user account/redirect
// ...
// delete token so it can't be used again
$query = $db->prepare(
"DELETE FROM pending_users WHERE username = ? AND token = ? AND tstamp = ?",
);
$query->execute(
array(
$username,
$token,
$tstamp
)
);
Assuming you have tables like the ones in my queries. Hope i answered you well
There is a service called fos_user.security.login_manager that can help:
public function loginByTokenAction($token)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('AppBundle:User')->findOneByToken($token);
$this->container->get('fos_user.security.login_manager')->loginUser('firewall_name', $user);
// ...
}
source : https://github.com/symfony/symfony/pull/13062
This isn't a post about a problem I'm having right now. I'm planning on using the Laravel Socialite plugin in my App, I already have a fully working login/register system using Laravel's built in Auth system but I'm wondering if there is a good way to allow users that already have their accounts in my app to link their social profiles to the already existing account?
The plan would be that they can login with their username and password or any of their social profiles. All the research I've done so far explains how to set up Socialite and utilise that data for registering/logging in new users, however I can't find anything to marry the two together.
I'm not looking for anyone to write this for me so please don't get confused by this post.
Thanks for any info,
Andy
Absolutely.
I have FB and Twitter OAuth on one site. The way I do it is by adding oauth_facebook_id and oauth_twitter_id columns to the users table (as well as an avatar column, if you want that).
I have the routes set up as /auth/{provider} and /auth/{provider}/callback, so /auth/facebook and /auth/facebook/callback for example.
In my handleProviderCallback($provider) method on my AuthController, I grab the returned user's details and check to see if they already exist in my database with their email, Facebook OAuth ID or Twitter OAuth ID. If they exist, I set their email, OAuth IDs and their avatar, if they don't exist I create them. Then I log them in.
$user = Socialite::driver($provider)->user();
$user_query = User::where('email', $user->email)
->orWhere('oauth_facebook_id', $user->id)
->orWhere('oauth_twitter_id', $user->id)
->get();
if($user_query->count() > 0) {
$the_user = $user_query->first();
if($provider === 'facebook') {
$the_user->oauth_facebook_id = $user->id;
}
if($provider === 'twitter') {
$the_user->oauth_twitter_id = $user->id;
}
$the_user->avatar = $user->avatar;
$the_user->save();
\Auth::login($the_user, true);
return redirect('/');
}
$new_user = User::create([
'name' => $user->name,
'email' => $user->email,
'oauth_facebook_id' => $provider === 'facebook' ? $user->id : NULL,
'oauth_twitter_id' => $provider === 'twitter' ? $user->id : NULL,
'avatar' => $user->avatar
]);
\Auth::login($new_user, true);
return redirect('/');
Hopefully this helps you make sense of how to do it.
I have never used sessions before and I am trying to figure out the best way to handle this. I basically am trying to do:
1 step selecting a service
2 step selecting a time
3 step review and book
I can get it to work with no problems using mysql. What I would usually do is save the information into the database after each step and by the time I get to the review part I would have all the information saved and was OK.
However I don't think this is the best way to approach this and might cause problems down the road (what if they stopped at step 2 blah blah)
I decided to try the Laravel 4 sessions and it was super easy to save the session and move on to the next step. However, when I get to the final step I need to join mysql tables to fully show the information about their booking. Can I use the session information to join the information? Can I use the Sessions Database to save this information? Or use different tables?
My controller that POST after reviewing the information:
public function getReview() {
//sets id of user
$user = User::find(Auth::user()->id);
//gets the time and date that they booked from #getSchedule
$scheduler = Session::get('schedule');
//formats time to put in db
$date = strtotime($scheduler['date']);
//same thing as the line above
$dateFormat = date('Y-m-d',$date);
//model to save the schedule
$schedule = new Schedule();
$schedule->userID = $user->id;
$schedule->date = $dateFormat;
$schedule->block = $scheduler['timeslot'];
$schedule->status = "1";
$schedule->save();
//gets the services the user picked from #getServices
$service = Session::get('service');
//saves the services as individual rows in db table
foreach($service as $services)
{
if(!empty($services)) {
$service = new Service();
$service->userID = $user->id;
$service->services = $services;
$service->save();
}
}
return Redirect::to('dashboard');
}
This is the GET review page (where I am having the issues with all the JOINS)
public function showReview() {
$user = User::find(Auth::user()->id);
//show the information and confirm that they want all this crap...if they do..save it and return them to their dashboard
$time = DB::table('bk_schedule')
->leftJoin('bk_timeslot', 'bk_schedule.block', '=', 'bk_timeslot.id')
->where('bk_schedule.id', Auth::user()->id)->first();
$date = strtotime($time->date);
$dateFormat = date('D: F d, Y',$date);
$service = Session::get('service');
$serviceSummary = DB::table('bk_service')
->leftJoin('pr_service', 'pr_service.id', '=', 'bk_service.services')
->where('bk_service.userID', Auth::user()->id)
->get();
$total = DB::table('bk_service')
->leftJoin('pr_service', 'pr_service.id', '=', 'bk_service.services')
->where('bk_service.userID', Auth::user()->id)
->sum('pr_service.price');
return View::make('book.review', array('pageTitle' => 'Booking Appointment Review and Finalize', 'service' => $service, 'date' => $dateFormat,
'time' => $time, 'email' => $user->email, 'serviceSummary' => $serviceSummary, 'total' => $total));
}
Is it possible to save the information at the GET and delete it if they don't submit to POST? Could I maybe use my session data to and use the MySQL queries I have?
You don't understand what the session is, with you approach users will not be able to fill several forms (open in several tabs) simultaneously.
So, general way to do this is:
First page shows just HTML code with some fields
User selects them and POST data back to server
Server validates data and open ANOTHER HTML page with new fields AND adds several "hidden" field with values selected in step 1 (of course server can present page1 with error messages)
Users posts this form, server can open THIRD form where new visible fields and ALL previous fields are stored in hidden inputs
Finally user posts form to last page, where you have all data from all previous pages.
Just to notice: another approach is to store "temporary" data in session, for this you will need to obtain some generated ID on 2nd step and pass it through pages as described before
I'm trying to retrieve profile information regarding users who allowed my app to collect data about them (birthday, location etc.). This works fine when, and only when, the user actually visits my app and authenticates with facebook through token exchange.
However, I want to be able to routinely check if the user location hasn't changed, also if a user adds information about himself on FB, I want to "sync" it with my app, so that the information stays the same.
So I save the users access token, and later on, with a cron job I use the token to get his profile:
$user = mysql_query ... <- get data from mysql
and then:
$user_profile = $facebook->api('/'.$user['uid']);
And it works, of course, but all the data I want from using extended permissions - just doesn't show up!
Is the the curse of the offline_access disabling? Or do I need to use a different way of obtaining such data. I tried adding ?fields=birthday and etc. on the end of the query, but it still doesn't work.
Not sure if this is what you meant by 'offline_access disabling' however Facebook are deprecating offline_access as you can see in the roadmap:
http://developers.facebook.com/roadmap/
You can read more information on the deprecation here:
http://developers.facebook.com/roadmap/offline-access-removal/
Also, are you correctly setting the access token received from the database?
$facebook->setAccessToken($user['access_token']);
Found out how to accomplish this - the access token has to be passed as an additional parameter on the api queries, like such:
$user_profile = $facebook->api('/'.$user['uid'].'', array('access_token' => $session['access_token']));
In case anyone wonders onto this page, here is a way to use saved access tokens, to access full information about the user you want, without having to authenticate:
<?
require "facebook.php";
$facebook = new Facebook(array(
'appId' => your_app_id,
'secret' => your_app_secret
));
// get the saved user details
$user = mysql_fetch_assoc(mysql_query('SELECT * FROM `facebook_tokens` WHERE `id` = "1"'));
$session = array(
'access_token' => $user['access_token'],
'secret' => $user['secret'],
'session_key' => $user['session_key'],
'uid' => $user['uid']
);
$sig = '';
foreach($session as $key => $value) {
$sig .= implode('=', array($key, $value));
}
$session['sig'] = md5($sessionStr.'Your App Secret');
$facebook->setSession($session, false);
$user_profile = $facebook->api('/'.$session['uid'], array('access_token' => $session['access_token']));
echo '<pre>';
print_r($user_profile);
echo '</pre>';
?>