I am creating a twitter log in feature for my project, the oauth step where the user has granted permission for my app to use their data returns the user to the /twitter-auth route, this route in turn initiates this method:
public function auth() {
/* Oauth token */
$token = Input::get('oauth_token');
/* Verifier token */
$verifier = Input::get('oauth_verifier');
/* Request access token */
$accessToken = Twitter::oAuthAccessToken($token, $verifier);
/* Set the session variables from the acccess token above */
Session::set('user_id', $accessToken['user_id']);
Session::set('username', $accessToken['screen_name']);
Session::set('oauth_token', $accessToken['oauth_token']);
Session::set('oauth_token_secret', $accessToken['oauth_token_secret']);
/* Determine if the user already exists in the database, if he/she does, then
only update the user, otherwise, store a new user. Also pass an instance of the
accessToken as flash data in both instances. */
if( User::where('twitter_id', $accessToken['user_id'])->first() == null )
{
$newUser = array(
'username' => $accessToken['screen_name'],
'oauth_token' => $accessToken['oauth_token'],
'oauth_token_secret' => $accessToken['oauth_token_secret'],
'twitter_id' => $accessToken['user_id']
);
User::create( $newUser );
return Redirect::to('/');
}
else
{
$userToUpdate = User::where('twitter_id', Session::get('user_id'))->first();
$userToUpdate->username = $accessToken['screen_name'];
$userToUpdate->oauth_token = $accessToken['oauth_token'];
$userToUpdate->oauth_token_secret = $accessToken['oauth_token_secret'];
$userToUpdate->twitter_id = $accessToken['user_id'];
$userToUpdate->save();
return Redirect::to('/');
}
}
The user is saved/updated as necessary, but the user is not redirected to the home page. This happens with the redirect code both inside and outside of the IF statement. I was wondering if anyone could give me any clues as to why the redirect isn't working?
You are missing a return
your function in this case auth() is returning the Redirect object but is the function calling your auth() function is returning the result back to the controller?
Please make sure that in your controller, you return the Redirect class that is from auth() function.
Just tested your code and works :
let's say you have a UserController :
routes.php
Route::get('twitter-auth',array('as'=>'twitter-auth', 'uses'=>'UserController#twitterAuth'));
UserController
the user model class is just passed by dependency injection, to test this part also.
<?php
class UserController extends BaseController {
public function __construct(User $u){
$this->user = $u;
}
public function twitterAuth(){
return $this->user->auth();
}
}
User model :
I had to modify the code a little to fit my setup also
public function auth(){
/* Oauth token */
$token = Input::get('oauth_token');
/* Verifier token */
$verifier = Input::get('oauth_verifier');
/* Request access token */
//$accessToken = Twitter::oAuthAccessToken($token, $verifier);
//emulate the request of access Token
$accessToken = [
'user_id'=>'11',
'screen_name'=>'fewfewfew',
'oauth_token'=>'12312321',
'oauth_token_secret'=>'12312232323'
];
/* Set the session variables from the acccess token above */
Session::set('user_id', $accessToken['user_id']);
Session::set('username', $accessToken['screen_name']);
Session::set('oauth_token', $accessToken['oauth_token']);
Session::set('oauth_token_secret', $accessToken['oauth_token_secret']);
/* Determine if the user already exists in the database, if he/she does, then
only update the user, otherwise, store a new user. Also pass an instance of the
accessToken as flash data in both instances. */
if( User::where('twitter_id', $accessToken['user_id'])->first() == null )
{
$newUser = array(
'username' => $accessToken['screen_name'],
'oauth_token' => $accessToken['oauth_token'],
'oauth_token_secret' => $accessToken['oauth_token_secret'],
'twitter_id' => $accessToken['user_id']
);
User::create( $newUser );
return Redirect::to('/');
}
else
{
$userToUpdate = User::where('twitter_id', Session::get('user_id'))->first();
$userToUpdate->username = $accessToken['screen_name'];
$userToUpdate->oauth_token = $accessToken['oauth_token'];
$userToUpdate->oauth_token_secret = $accessToken['oauth_token_secret'];
$userToUpdate->twitter_id = $accessToken['user_id'];
$userToUpdate->save();
return Redirect::to('/');
}
}
Let me know if this is what you wanted
Returning a Redirect to execute it is only possible from routes, controller actions and filters. Otherwise you have to call send()
Redirect::to('login')->send();
Related
I'm developing a webpage with Laravel 8 and I have issues with fetching a patron details by id from Patreon API. Here is my use case.
I’ve added "Login with Patreon" option to my webpage, and it works well. When someone login with Patreon successfully, I store her/his Patreon id and set remember token to login the member automatically when she/he visits my page next time.
The first login process is fine. The problem occurs when my Patron visits my page next time. Because I want to check whether I received any payment before I let she/he see all content. That’s why I need to get my patron details from a middleware. To do that I tried:
fetch_user() returns my account details instead of logged-in user.
fetch_user() with the access token that returns from Patreon when
someone login, returns unauthorized.
fetch_member_details() doesn’t work with the id I passed, which is an
integer like 5484646 because it requires a very long string like
55153fds-f45fd5sfs-fds42ds, I don't know what it's.
fetch_page_of_members_from_campaign() and fetch_member_details()
together to get the proper ID, but it takes ages to get data, which
is unacceptable.
So, how can it be done?
https://further-reading.net/2020/06/getting-names-of-your-patreon-patrons-by-tier/
This might be useful. I believe, there is not a direct single API for this, but you can -
First fetch all campaigns/tiers data
And then fetch patrons for each campaign/tier
I like to answer my question for those who need some help.
First of all, I use the official PHP package by Patreon
I've created a middleware to check if the user should be authorized again. In order to prevent the same process every single time, I set timeout to users table and check if it still has time to expire. If it does, no need to do anything. Of course, this is my use case, but without that explanation, some parts of the code can be nonsense to you.
// App\Http\Middleware\AuthenticateMember.php
public function handle(Request $request, Closure $next)
{
if (!Auth::check()) {
return $next($request);
}
if (Carbon::parse(Auth::user()->timeout)->isFuture()) {
return $next($request);
}
$this->refreshCredentials();
return $next($request);
}
If "timeout" isn't in the future, refreshCredentials method will be called. This is a method, which will trigger binding AuthGatewayContract to the service container.
// App\Trait\Users.php
public function refreshCredentials()
{
$gateway = App::make('App\Services\AuthGatewaysContract');
$gateway->ensureUserStillAuthenticated();
}
public function handleUserRecord($user)
{
return User::updateOrCreate([
'email' => $user['email']
], $user);
}
public function attemptToLogin($user, $remember = true)
{
Auth::login($user, $remember);
event(new Registered($user));
}
This is how the binding works:
// App\Providers\AppServiceProvider.php
public function register()
{
$this->app->singleton(AuthGatewaysContract::class, function () {
$routeParts = explode('/', url()->current());
$gateway = array_pop($routeParts); // this is how I know which "Login with ..." button is clicked.
$isGateway = Gateway::where('name', $gateway)->first();
$gateway = $isGateway ? ucfirst($gateway) : ucfirst(Auth::user()->gateway->name);
$class = "\App\Services\AuthGateways\\$gateway";
return new $class();
});
}
So Patreon.php is active gateway now, and ensureUserStillAuthenticated can be called:
// App\Services\AuthGateways\Patreon.php
public function ensureUserStillAuthenticated()
{
$this->authenticate([
'access_token' => Auth::user()->access_token,
'refresh_token' => Auth::user()->refresh_token,
]);
}
private function authenticate($tokens)
{
$patron = $this->fetchUserFromGateway($tokens);
$user = $this->handleResponseData($patron, $tokens);
$user = $this->handleUserRecord($user);
return $this->attemptToLogin($user);
}
private function fetchUserFromGateway($tokens)
{
// This is the only function that communicate with Patreon-php package.
$api_client = new API($tokens['access_token']);
return $api_client->fetch_user();
}
private function handleResponseData($data, $tokens)
{
return [
'name' => $data['data']['attributes']['full_name'],
'email' => $data['data']['attributes']['email'],
'password' => Hash::make(Str::random(24)),
'role_id' => $this->assignRoleId($data),
'payment_id' => Payment::where('name', 'patreon')->first()->id,
'gateway_id' => Gateway::where('name', 'patreon')->first()->id,
'access_token' => $tokens['access_token'],
'refresh_token' => $tokens['refresh_token'],
'timeout' => Carbon::today()->addMonth()->toDateString()
];
}
I am using oauth2-microsoft to develop a 'sign in with Microsoft' tool for my app. I'm successfully authenticating and receiving a token, but then I receive an error from the sample code.
I am using the sample code below and have tried various combinations of URLs in the 'urlResourceOwnerDetails' field, including leaving it blank.
$provider = new \Stevenmaguire\OAuth2\Client\Provider\Microsoft([
'clientId' => '<redacted>',
'clientSecret' => '<redacted>',
'redirectUri' => 'http://localhost/test.php',
'urlAuthorize' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
'urlAccessToken' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
'urlResourceOwnerDetails' => 'https://graph.microsoft.com/v1.0/me/drive'
]);
$options = [
'scope' => ['wl.basic', 'wl.signin']
];
After this comes authentication and token generation.
Then this line throws errors:
$user = $provider->getResourceOwner($token);
A token is definitely being generated, as I can echo $token and see it.
The above code should create a $user object that contains details about the logged in user. However, instead it generates these errors:
If 'urlResourceOwnerDetails' is set to https://graph.microsoft.com/v1.0/me/drive I get:
League\OAuth2\Client\Provider\Exception\IdentityProviderException: Access token is empty
If 'urlResourceOwnerDetails' is set to https://outlook.office.com/api/v2.0/me I get:
UnexpectedValueException: Invalid response received from Authorization Server. Expected JSON.
And if 'urlResourceOwnerDetails' is empty I get:
GuzzleHttp\Exception\RequestException: cURL error 3: malformed (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)
Any ideas, please?
It appears oauth2-microsoft does not support Microsoft Graph Auth to a full extent at the moment, refer for example this thread
Regarding the error
League\OAuth2\Client\Provider\Exception\IdentityProviderException:
Access token is empty
access token is expected to be passed as Authorization header but according to Microsoft.php provider implementation it is passed instead as query string:
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
$uri = new Uri($this->urlResourceOwnerDetails);
return (string) Uri::withQueryValue($uri, 'access_token', (string) $token);
}
The way how library is designed, the following provider class could be introduced to support Microsoft Graph calls (by including access token in the Authorization header of a request)
class MicrosoftGraphProvider extends AbstractProvider
{
/**
* Get provider url to fetch user details
*
* #param AccessToken $token
*
* #return string
*/
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
return 'https://graph.microsoft.com/v1.0/me';
}
protected function getAuthorizationHeaders($token = null)
{
return ['Authorization'=>'Bearer ' . $token->getToken()];
}
public function getBaseAuthorizationUrl()
{
return 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
}
public function getBaseAccessTokenUrl(array $params)
{
return 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
}
protected function getDefaultScopes()
{
return ['openid profile'];
}
protected function checkResponse(\Psr\Http\Message\ResponseInterface $response, $data)
{
// TODO: Implement checkResponse() method.
}
protected function createResourceOwner(array $response, AccessToken $token)
{
return (object)$response;
}
}
I need to write a functional test in order to test that each role has the correct access to the pages.
In order to do that, I'm simulating authentication with a token, but I slightly edited the logIn method, just to call it with custom $username, $role and $firewall:
protected function logIn($username, $role, $firewall)
{
$session = $this->client->getContainer()->get('session');
$token = new UsernamePasswordToken($username, null, $firewall, $role);
$session->set('_security_' . $firewall, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
So I am able to call it specifying which roles should have the fake user:
$this->logIn('my_username#example.com', ['ROLE_USER'], "my_firewall");
Then I can test if the user is not allowed or not to access certain routes:
// check if the access is correctly denied to the ROLE_USER
$this->client->request('GET', '/route-not-allowed-to-user');
$this->assertEquals(403, $this->client->getResponse()->getStatusCode());
// check if the access is correctly allowed to the ROLE_USER
$this->client->request('GET', '/route-allowed-to-user');
$this->assertNotEquals(403, $this->client->getResponse()->getStatusCode());
Those assertions work, the only problem is that in the view of the route-allowed-to-user I'm using twig to output the username:
{{ app.user.username }}
but it fails. I got status code 500 instead of getting 200, and the following error:
Impossible to access an attribute ("username") on a null variable ...
because app.user is not set.
How can I correctly set the user when simulating an authentication with token?
I think this happens because you didn't go through the authentication process and just created the user token which didn't trigger Symfony's event that store the user's username, roles and so on.
I did a similar thing recently by actually going through login form, filling data and sending it. Just like I was doing a real login attempt and it works well.
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class AuthenticatedTestCase extends KernelTestCase
{
static protected $client;
static public function setUpBeforeClass()
{
parent::setUpBeforeClass();
self::$client = static::$kernel->getContainer()->get('test.client');
}
static public function login($login, $password)
{
$crawler = self::$client->request('GET', '/test_admin/login');
$form = $crawler->filter('input[type="submit"]')->form([
'_username' => $login,
'_password' => $password,
]);
self::$client->submit($form);
// Redirect after successful login
self::assertEquals(302, self::$client->getResponse()->getStatusCode());
self::$client->followRedirect();
if (200 === self::$client->getResponse()->getStatusCode()) {
// Redirected URL is OK
// ...
}
}
}
I've resolved by editing the logIn method as follows:
protected function logIn($username, $password, $firewall)
{
$session = $this->client->getContainer()->get('session');
$authenticationManager = $this->client->getContainer()->get('security.authentication.manager');
$token = $authenticationManager->authenticate(
new UsernamePasswordToken(
$username,
$password,
$firewall
)
);
$session->set('_security_' . $firewall, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
and using doctrine data fixtures in order to set users and roles.
I have already written an application in a procedural way and am trying to move into into a Laravel framework. I'm having trouble with the SOAP exchange section as I am getting an ID value that authenticates the user but cannot access that value (as a cookie) later in the program to authenticate the search.
Here is my code so far:
<?php namespace App;
use Artisaninweb\SoapWrapper\Facades\SoapWrapper;
use Illuminate\Http\RedirectResponse;
class SoapController {
private $auth_response;
private $cookie;
private $search_client;
private $search_response;
public function soapExchange() {
// create SOAP client and add service details
SoapWrapper::add(function ($service) {
$service
->name('WoSAuthenticate')
->wsdl('http://search.webofknowledge.com/esti/wokmws/ws/WOKMWSAuthenticate?wsdl')
->trace(true)
->cache(WSDL_CACHE_NONE);
});
SoapWrapper::service('WoSAuthenticate', function($service) {
// call authenticate() method to get SID cookie
$auth_response = $service->call('authenticate', []);
$cookie = $auth_response->return;
// test for cookie return
// print($cookie);
});
// create SOAP client and add service details
$search_client = new SoapWrapper;
$search_client::add(function ($service) {
$service
->name('WoSSearch')
->wsdl('http://search.webofknowledge.com/esti/wokmws/ws/WokSearch?wsdl')
->trace(true)
->cache(WSDL_CACHE_NONE);
});
if (isset($auth_response->return)) {
// if there is an SID returned then add it to the cookie attribute of the search client
$search_client->__setCookie('SID', $cookie);
} else {
// route to relevant view to display throttle error
return redirect('throttle');
}
}
}
I am successfully retrieving the response from the Web API call and getting a code to authenticate the user, saved as $cookie. However, I need then to create another SoapWrapper for performing the search and this needs the ID code attached by using the __setCookie method. If nothing is returned by the authenticate call then it redirects to an error message via throttle.blade.php elsewhere.
Surely there is a way to return a value created from a function so that it can be used elsewhere?
** EDIT **
Looked into employing SoapClient instead and including all operations within a single function. It all relates to a specific Web API anyway so I guess separation of concerns is not so much of an issue. FYI the new class I am trying is this:
<?php namespace App\Models;
use SoapClient;
use Illuminate\Http\RedirectResponse;
class SoapWrapper {
public function soapExchange() {
// set WSDL for authentication and create new SOAP client
$auth_url = "http://search.webofknowledge.com/esti/wokmws/ws/WOKMWSAuthenticate?wsdl";
// array options are temporary and used to track request & response data
$auth_client = #new SoapClient($auth_url);
// set WSDL for search and create new SOAP client
$search_url = "http://search.webofknowledge.com/esti/wokmws/ws/WokSearch?wsdl";
// array options are temporary and used to track request & response data
$search_client = #new SoapClient($search_url);
// run 'authenticate' method and store as variable
$auth_response = $auth_client->authenticate();
// call 'setCookie' method on '$search_client' storing SID (Session ID) as the response (value) given from the 'authenticate' method
// check if an SID has been set, if not it means Throttle server has stopped the query, therefore display error message
if (isset($auth_response->return)) {
$search_client->__setCookie('SID',$auth_response->return);
} else {
return Redirect::route('throttle');
}
}
}
Maybe try $GLOBALS?
<?php
$GLOBALS[data] = "something";
function abc(){
echo $GLOBALS[data];
}
?>
use Artisaninweb\SoapWrapper\Facades\SoapWrapper;
class SoapController extends Controller {
public $resultSoapStatus;
public $resultSoapAuthority;
public function heySoap{
SoapWrapper::add(function ($service) ...
$data = [
'MerchantID' => $MerchantID,
'Amount' => $Amount,
'Description' => $Description,
'Email' => $Email,
'Mobile' => $Mobile,
'CallbackURL' => $CallbackURL
];
SoapWrapper::service('test', function ($service) use ($data) {
$resultSoap = $service->call('PaymentRequest', [$data]);
$this->resultSoapStatus = $resultSoap->Status;
$this->resultSoapAuthority = $resultSoap->Authority;
});
if($this->resultSoapStatus == 100 && strlen($this->resultSoapAuthority) == 36)
{
//Do Something
}
else
{
return Redirect::back();
}
}
}
Enjoy bro
Could anybody brief about user_token functionality in Auth module? What is a use and how this incorporates in Auth module?
It is used when a user checks the 'Remember me' box on your site. A token is generated for the user and stored in the user_tokens table.
If you look at the Kohana_Auth_ORM class in the _login function, you can see how it is created:
if ($remember === TRUE)
{
// Create a new autologin token
$token = ORM::factory('user_token');
// Set token data
$token->user_id = $user->id;
$token->expires = time() + $this->config['lifetime'];
$token->save();
// Set the autologin cookie
cookie::set('authautologin', $token->token, $this->config['lifetime']);
}
It is used by the auto_login() function also in the Kohana_Auth_ORM class:
/**
* Logs a user in, based on the authautologin cookie.
*
* #return boolean
*/
public function auto_login()
{
if ($token = cookie::get('authautologin'))
{
// Load the token and user
$token = ORM::factory('user_token', array('token' => $token));
if ($token->loaded() AND $token->user->loaded())
{
if ($token->user_agent === sha1(Request::$user_agent))
{
// Save the token to create a new unique token
$token->save();
// Set the new token
cookie::set('authautologin', $token->token, $token->expires - time());
// Complete the login with the found data
$this->complete_login($token->user);
// Automatic login was successful
return TRUE;
}
// Token is invalid
$token->delete();
}
}
return FALSE;
}
It is up to you to correctly use this capability within your authorization controller. I'm relatively new to Kohana, but I perform a simple check to redirect a user if they go to the login form and are already logged in or can automatically login:
if (Auth::instance()->logged_in() || Auth::instance()->auto_login())
Request::instance()->redirect('auth/');
The code for the Auth module isn't too difficult to understand. If you're new to Kohana, it's a good starting point to see how the ORM module works.