I'm currently implementing OAuth2 in my website to keep an user logged in inside an Android app. I want to change the default OAuth2 user database to my own user database. Unfortunately I can't find out how to do that. It should be possible with overriding classes and without changing the code in the core library, but how to do it?
This is what I have in my server.php file:
// Autoloading (composer is preferred, but for this example let's just do this)
require_once('/src/OAuth2/Autoloader.php');
OAuth2\Autoloader::register();
$storage = new OAuth2\Storage\Pdo(array('dsn' => 'mysql:host=xxxx;dbname=xxxx', 'username' => 'xxxx', 'password' => 'xxxx'));
// Pass a storage object or array of storage objects to the OAuth2 server class
$server = new OAuth2\Server($storage);
// Add the "Password / User Credentials" grant type
$server->addGrantType(new OAuth2\GrantType\UserCredentials($storage));
So here I want to use to my own user table instead of the default oauth_users table. And because the passwords are salted I need to have a different password check too. I am using the BShaffer OAuth2 Library: https://github.com/bshaffer/oauth2-server-php
With this library it's easy to write custom code so you don't have to touch the core of this library.
For this problem you'll have to create a custom storage class that implements the UserCredentialsInterface. There are two methods in here which you need to implement yourself
public function checkUserCredentials()
public function getUserDetails()
Here you can implement your logic for checking user details and fetching user details.
After this you'll need to add this storage to the oAuth server like this:
$server = new OAuth2\Server($storage);
$userStorage = new YourCustomUserStorage();
$server->addStorage($userStorage, 'user_credentials');
You'll also need to pass this storage to any Grant type you're adding to the server, in your case it looks like this:
$server->addGrantType(new OAuth2\GrantType\UserCredentials($userStorage));
Related
Background: I am trying to set up single sign on (SSO) for users such that they can authenticate to my website and not have to authenticate a second time to our third-party MSP's website. Ideally, the user clicks a link on our website and is taken to the third-party site already logged in and landing on the dashboard (if the account doesn't exist, it is created during this step). We are not using SAML for authentication as a security feature, so all that we need the SAML code for is just producing cookies that prevent the user from having to log in again when he/she gets to our vendor's site. This third party MSP does not support authentication via API or web service and therefore I have been tasked with implementing SAML, their only supported SSO method. I am new to SAML (but not PHP or development) and have been learning as I go. I am told it will support the goals described above.
I initially tried using LDAP as the authentication source as this is what I use for authentication to my website, but this resulted in me getting directed to a login page with no discernible way to instead just pass parameters to SimpleSAMLphp to tell it "the user is already authenticated, all I need you to do is give me valid cookies so I can get past the third party website's authentication checks".
So I switched to writing a custom authentication module. I opened up the GitHub for SimpleSAMLphp and used the "UserPassBase" class as an example to create my own authentication module that inherits from the "Source" class. Because I don't need to re-authenticate the user against LDAP a second time since they're already logged in to our website, I created a simple "authenticate" function that just sets the $state['Attributes'] array.
Here is the code for my custom module:
<?php
namespace SimpleSAML\Module\productauth\Auth\Source;
use SimpleSAML\Auth;
/**
Author: Joey
Class developed to be used as a custom authentication module for simpleSAMLphp. This class will take an existing session from a product website and use it to create a SAML session and redirect to a website.
**/
class ProductAuth extends \SimpleSAML\Auth\Source {
const STAGEID = '\SimpleSAML\Module\productauth\Auth\ProductAuth.state';
const AUTHID = '\SimpleSAML\Module\productauth\Auth\ProductAuth.AuthId';
private $user;
public function __construct($info, $config) { // parameters aren't used, just filler from base class
$info = array("AuthId" => "productauth");
parent::__construct($info, $config);
}
public function login($user, $redirectURL) {
$this->user = $user; // normally I'd set this in the constructor, but the overload has my hands tied as far as function definitions go
$this->initLogin($redirectURL); // calls authenticate function and then, if no exceptions, parent::loginCompleted which redirects to the given URL
}
public function authenticate(&$state) { // called by parent::initLogin
$state[self::AUTHID] = $this->authId;
$state['Attributes'] = [
'uid' => [$this->user->uid],
'givenName' => [$this->user->givenName],
'sn' => [$this->user->sn],
'mail' => [$this->user->mail]
];
$id = Auth\State::saveState($state, self::STAGEID);
}
}
?>
I am calling it from a controller class on my website:
private function goToTrainingSite() {
require_once("../third-party-libs/simplesamlphp/_include.php");
global $TRAINING_URL;
$user = $_SESSION['subject']->user;
$samlObj = new SimpleSAML\Module\productauth\Auth\Source\ProductAuth(array(), array());
$samlObj->login($user, $TRAINING_URL);
}
I mimicked the flow of the "UserPassBase" class (https://github.com/simplesamlphp/simplesamlphp/blob/master/modules/core/lib/Auth/UserPassBase.php), but it seems that despite all of my authentication working and setting a SimpleSAMLAuth cookie, when the parent::loginCompleted function in the "Source" class (https://github.com/simplesamlphp/simplesamlphp/blob/master/lib/SimpleSAML/Auth/Source.php) runs, it redirected me to the third party site. I then see the following in the logs:
SAML2.0 - IdP.SSOService: incoming authentication request: [REDACTED DATA]
Session: 'productauth' not valid because we are not authenticated.
I have been trying for 3 days to figure out why it seems as though despite setting SimpleSAML session cookies with a completed, successful authentication, that upon receiving the auth request from the SP, my SimpleSAMLphp code just pretends to not know about the completed auth and tries to authenticate again... but because it is not being called from my code, it doesn't have access to the $user variable which contains all of the attributes I need to place on the user when he/she authenticates to this third party website. It seems that when it receives an authentication request, my SimpleSAMLphp installation starts a new session and tries a brand new authentication.
I have delved into a lot of the code of SimpleSAMLphp and tried to understand what is going on, but it seems that there is just no reasonable way to authenticate by calling an authentication source from PHP code and being able to skip the SP-initiated authentication. I have tried:
Using the SimpleSAML API (https://simplesamlphp.org/docs/stable/simplesamlphp-sp-api) to call my authentication source, but there seems to be no way to pass that $user variable I need the attributes from.
Trying to load the cookies in the "Session" class when it is checking for valid sessions... but it seems like the cookies from the successful auth session initiated by my code are just gone and nowhere to be found.
I decided to stop focusing on trying to get the $user variable and the data I needed to the second authentication, and instead focus on WHY the second authentication was even happening. I looked at the cookies and thought about how the data was being retrieved, and made a correct hunch that our application's custom session handler might be at fault for SimpleSAMLphp's inability to recognize the first authentication. Our custom session handler stores our sessions in the database, but SimpleSAMLphp expects to use the default PHP session handler to manage its session. Therefore, my first authentication was being sent to the database and when SimpleSAMLphp started looking for it where PHP sessions are usually stored, it didn't see it and assumed it needed to kick off another authentication session from scratch.
Using SimpleSAMLphp's documentation for service providers and a lot of my own debugging, I changed the function in my controller like so:
private function goToTrainingSite() {
require_once ("../third-party-libs/simplesamlphp/_include.php");
global $TRAINING_URL;
$joeySiteSession = $_SESSION;
$user = $_SESSION ['subject']->user; // save user to variable before the Joey's Site session is closed
session_write_close (); // close Joey's Site session to allow SimpleSAMLphp session to open
session_set_save_handler ( new SessionHandler (), true ); // stop using SessionHandlerJoey and use default PHP handler for SimpleSAMLphp
$samlObj = new SimpleSAML\Module\joeysiteauth\Auth\Source\JoeySiteAuth ( array (), array () );
$samlObj->login ( $user, function () { return;} ); // use custom authentication module to set atttributes and everything SimpleSAMLphp needs in the auth session/cookie
$session = \SimpleSAML\Session::getSessionFromRequest ();
$session->cleanup (); // must call this function when we are done with SimpleSAMLphp session and intend to use our Joey's Site session again
session_write_close ();
$_SESSION = $joeySiteSession; // restore Joey's Site session
header ( "Location: {$TRAINING_URL}" );
}
I use AWS Services regularly and have my PHP SDK automatically retrieve credentials from my ec2 instance when I connect with Amazon.
I now have a library that I want to use which also requires my AWS secret key and access key to be included when I instantiate the class.
How can I retrieve the current access token and secret key through the AWS PHP SDK so I don't hard code keys into my application?
Where are you storing your AWS Credentials? In a credentials file or IAM Role?
[EDIT after the OP provided specific use case details]
From the link that you provided modify the example to look like this. Note: I have not tested the code, but this will be close:
// Require Composer's autoloader
require_once __DIR__ . "/vendor/autoload.php";
use Aws\Credentials\Credentials
use Aws\Credentials\CredentialProvider;
use Aws\Exception\CredentialsException;
use EddTurtle\DirectUpload\Signature;
// Use the default credential provider
$provider = CredentialProvider::defaultProvider();
$credentials = $provider()->wait();
$upload = new Signature(
$credentials->getAccessKeyId(),
$credentials->getSecretKey(),
"YOUR_S3_BUCKET",
"eu-west-1"
);
[END EDIT]
The simplest answer if you are using a credentials file is to open ~/.aws/credentials in a text editor and extract them. Otherwise follow the details below.
See the bottom for the actual answer on how to extract your access key once you have them loaded.
The following example will create a DynamoDB client using credentials stored in ~/.aws/credentials (normally created by the AWS CLI) from the profile named 'project1':
$client = new DynamoDbClient([
'profile' => 'project1',
'region' => 'us-west-2',
'version' => 'latest'
]);
However, usually you will want the SDK to locate your credentials automatically. The AWS SDK will search for your credentials in the following order (not all cases included):
Environment Variables (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, etc.)
In the default profile section of ~/.aws/credentials
EC2 IAM Role
Normally just use this example and let the SDK find the credentials for you:
use Aws\Credentials\CredentialProvider;
use Aws\S3\S3Client;
// Use the default credential provider
$provider = CredentialProvider::defaultProvider();
// Pass the provider to the client
$client = new S3Client([
'region' => 'us-west-2',
'version' => '2006-03-01',
'credentials' => $provider
]);
The SDK has a number of credential providers so that you can control exactly where your credentials are coming from.
PHP Class CredentialProvider
One item is that you mention Access Token. This means that you are using STS Assume Role type of access. The PHP SDK supports this also. Just dig into the documentation for STS:
PHP STS Client
Once you have loaded your credentials into a provider you can use the class Credentials to extract the three components (AccessKeyId, AcessKeySecret, SecurityToken):
PHP Class Credentials
So I have a RESTful API, but I want to be safe so that not everyone can do anything.
$app->get('/users' , function(Request $request, Response $response){
$sql = "SELECT * FROM users";
try{
// Get db object
$db = new db();
// Connect
$db = $db->connect();
$stmt = $db->query($sql);
$users = $stmt->fetchAll(PDO::FETCH_OBJ);
$db = null;
echo json_encode($users);
} catch(PDOException $e){
echo '{"error": {"text": '.$e->getMessage().'}}';
}
});
So when i go to http://localhost/API/users i get all users into a json table.
Inside my database my data are stored like [{"id":"1","username":"werknemer","password":"...","level":"1","name":"piet","surname":"jan","email":"pietjan#gmail.nl"}]
I would like everyone to see his own table through my API and if you are level 5.
Is there a solution for that?
Your example is pretty basic and it's a starting point for using some "auth" concept in your REST APIs.
First things first: Authentication != Authorization.
Split these two concepts, the first one is going to make a user registered and logged into your app, the second one makes the "hard work" that you are looking for in this example, so check if a specific user is able to do some stuff.
For authentication, you can provide all the methods that you want, but remember that in REST your app MUST be stateless and you should provide a token (passed via HTTP Headers) that will be used by your application for understanding if the user is LOGGED and CAN do some stuff.
That's the key concept: A token (see JWT or OAUTH) should be used for authorization, and a very basic authorization is: "USER LOGGED".
In your example, you should use the middlewares for filter the http request, and don't enter into the router callback if the user is not authorized (logged in || have not a minLevel:5).
Checkout JWT or OAuth2 for this kinda stuff for more info.
Check this out -> (https://github.com/damianopetrungaro/slim-boilerplate) for a basic example of JWT generation in a slim app (if you are going to use this boilerplate PLEASE do not use the md5 for hash password this is a pretty basic example)
You need to add authentication and then authorisation to your API.
Authentication is the process of knowing who is accessing the API. A good way to do this is to you OAuth 2. I like and use Brent Shaffer's OAuth 2.0 Server library. https://github.com/akrabat/slim-bookshelf-api/tree/master/api contains an implementation of an API that using OAuth 2 to authorise users.
Once you know who is logging in, you then need to limit their access based on their role (or level). This is called access control. I like the zend components for this. Try zend-permissions-rbac - there's a good article on how to use it on the ZF blog.
I need to get some automated ad insights using the Marketing Api. For this purpose I have created a System User via the Business Manager, and generated a System User access token with the ads_read permission.
Using this token then to make api calls and get a specific Campaign's Insights, with the FacebookAds php v2.6 sdk, I get the following error:
Uncaught exception 'FacebookAds\Http\Exception\PermissionException'
with message '(#275) Cannot determine the target object for this
request. Currently supported objects include ad account, business
account and associated objects.'
Does my app need to be whitelisted or am I missing something else? I noticed that next to the 'ads_read' permission there was this note that stated '(your App must be whitelisted)'.
Here is the sample code I'm using
<?php
define('VENDOR_DIR', 'vendor/'); // Path to the Vendor directory
$loader = require VENDOR_DIR.'autoload.php';
use FacebookAds\Api;
use FacebookAds\Object\Campaign;
// Initialize a new Session and instantiate an Api object
Api::init(
'xxxxxxxxxxxxxxxx', // App ID
'xxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxx' // System User Access Token
);
$api = Api::instance();
use FacebookAds\Object\Values\InsightsLevels;
$campaign = new Campaign('xxxxxxxxxxxxx');
$params = array(
'level' => InsightsLevels::CAMPAIGN,
);
$async_job = $campaign->getInsightsAsync(array(), $params);
$async_job->read();
while (!$async_job->isComplete()) {
sleep(1);
$async_job->read();
}
$async_job->getResult();
?>
It seems like your app doesn't have ads_read permission. Here you can find more information on how to ask for it: https://developers.facebook.com/docs/marketing-api/access
I am also following what this guy's asking storage of client credential on OAuth2 server about oauth2-server-php-docs. Although it answered one thing for the client secret, I wanna ask about the users credentials. Check the following code sample from oauth2-server-php-docs:
// create some users in memory
$users = array('bshaffer' => array('password' => 'brent123', 'first_name' => 'Brent', 'last_name' => 'Shaffer'));
// create a storage object
$storage = new OAuth2\Storage\Memory(array('user_credentials' => $users));
// create the grant type
$grantType = new OAuth2\GrantType\UserCredentials($storage);
// add the grant type to your OAuth server
$server->addGrantType($grantType);
It says that the user credentials will be stored in memory and that it doesn't say anything about password encryption. This is my use case -- I already have user credentials stored in MySQL with password encrypted using PHP password_hash(). So how can I match or use the line $storage = new OAuth2\Storage\Memory() if the $user['bshaffer']['password'] is just a plain text?
I have this question too. But if you read the notes in the doc, just below the code snippet you mentioned, it says:
Note: User storage is highly customized for each application, so it is highly recommended you implement your own storage using OAuth2\Storage\UserCredentialsInterface
I think we can implement our own user storage for the users model with the “ OAuth2\Storage\UserCredentialsInterface” interface.
I did this by having my own network architecture and extending the Pdo storage object implementing user-defined password_verify(). So what I did with this one I created mytoken.php that receives the post from the front-end request then sends a request again to 127.0.0.1 to the token.php with the AUTH user and password (client_secret). I did this way so that client_secret is not sent from the front-end request.