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.
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'm using Hybridauth 3 in my PHP app to make some periodical tweets on behalf of my account.
The app has all possible permissions. I'm giving it all permissions when it asks for them on the first auth step.
After that Twitter redirects me to the specified callback URL and there I'm getting a pair of access_token and access_token_secret.
But when I'm trying to make a tweet using these tokens - it gives me:
{"errors":[{"code":220,"message":"Your credentials do not allow access to this resource."}]}
Here's how I'm trying to make a tweet:
$config = [
'authentication_parameters' => [
//Location where to redirect users once they authenticate
'callback' => 'https://mysite/twittercallback/',
//Twitter application credentials
'keys' => [
'key' => 'xxx',
'secret' => 'yyy'
],
'authorize' => true
]
];
$adapter = new Hybridauth\Provider\Twitter($config['authentication_parameters']);
//Attempt to authenticate the user
$adapter->setAccessToken(/*tokens I've got from getAccessToken() on /twittercallback/*/);
if(! $adapter->isConnected()) {
// never goes here, so adapter is connected
return null;
}
try{
$response = $adapter->setUserStatus('Hello world!');
}
catch (\Exception $e) {
// here I've got the error
echo $e->getMessage();
return;
}
Tried to recreate tokens and key\secret pairs and passed auth process for the app many times, including entering password for my Twitter account (as suggested in some posts on stackoverflow) but still have this error.
P.S. According to this, Hybridauth has fixed the issue in the recent release.
It looks like you are using application authentication as opposed to user authentication. In order to post a tweet, you must authenticate as a user. Also, make sure your Twitter app has read/write privileges.
After comparing headers of outgoing requests from my server with the ones required by Twitter, I've noticed that Hybris doesn't add very important part of the header: oauth_token. At least it's not doing this in the code for Twitter adapter and for the scenario when you apply access token with setAccessToken(). It's just storing tokens in the inner storage but not initializing corresponding class member called consumerToken in OAuth1 class.
So to initialize the consumer token properly I've overridden the apiRequest method for Twitter class (before it used the defalut parent implementation) and added a small condition, so when consumer token is empty before the request - we need to try to init it.
public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [])
{
if(empty($this->consumerToken)) {
$this->initialize();
}
return parent::apiRequest($url, $method, $parameters, $headers);
}
I'm not sure that I've fixed it the best way, but as long as it's working - that's fine.
For your info setAccessToken was fixed in v3.0.0-beta.2 (see PR https://github.com/hybridauth/hybridauth/pull/880)
I faced the same error when implementing a sample app in clojure and the following resource was a huge help to sort out my confusion about application-only auth vs user authentication: https://developer.twitter.com/en/docs/basics/authentication/overview/oauth
I am working on a project and currently writing the backend. I have decided to implement it as a rest api since i need to write a web app as well as a mobile app. I am having problem understanding how do i login the user since rest api are stateless. I have read some material which mention basic authentication (sending login credentials) with each request or Oauth2.0. Basic authentication is not recommended and i don't understand why i should use Oauth2.0 because no third party will be using my api. My question is how should i implement login functionality and what are the standards ?
add passport package to your project ,see this for more info https://laravel.com/docs/5.4/passport
create password grant client
create new user with token
use retrofit or another package to call Laravel api
/* prepare httpClient */
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request orginal = chain.request();
Request.Builder builder = orginal.newBuilder();
builder.addHeader("Accept", "application/json");
if (tools.isAuthorized()) {
builder.addHeader("Authorization", "Bearer " + tools.getAccessToken());
}
builder.method(orginal.method(), orginal.body());
Request build = builder.build();
return chain.proceed(build);
}});
5- call api and get response then save user token.
You'll need to add a unique api_token column for your Users table.
$table->string('api_token', 60)->unique();
In Laravel 5.4, api.php holds the API routes, you'll need to use an out-of-the-box middleware auth:api, so you can authenticate requests by api_token.
Read more
http://bootstrapdojo.com/rest-api-laravel-5-4-with-token-authentication/
I guess you can create a REST API that offers CRUD operations on JSON web tokens.
I am experimenting with Drupal 8 as our customer websites. Our customers authenticate through our own authentication application at the moment which speaks to our document store (instead of MySQL) to authenticate a user and provide them with a unique session ID (JWT eventually but that's another day and conversation) which we can use to then query a REST API and get user data in any of our other self apps.
We are moving over from an old JSP based websites to drupal as our apps are now written in Symfony 3 but want our customer websites to be Drupal 8.
Here's the bit I am trying to work out. If I authenticate in our old website I want to be able to redirect to the Drupal 8 website with the session ID we have in our hand and use that to fetch a object back of our logged in user. I have this bit working fine but I now am in a position to say... Ok I have the user object back, the 3rd party service has said that session ID is valid so we know we are authenticated.
Please refer to the below flow chart. I want to be able to also authenticate in Drupal 8 manually. Is this possible (I am sure it is) and if so can someone point me in the right direction as to what I need/should be doing, API's I should be calling?
Thank you kindly and good day :)
You should use the External Auth module.
A good exemple of how use this module is the SimpleSamlPHP Auth
Ok so it turned out not to be that tricky in the end. I thought I would have to extend and implement various class and create my own provider (which is probably the best practice) but for KISS sake I found another way.
Create a user first if one does not exists based on the user data I get back from my external service. Then pass that created user to the user_login_finalize method (why are a lot of methods underscored Drupal...) which then authenticated my user.
public function inbound(Request $request)
{
// Point the guzzle client to our external session service.
$client = new GuzzleHttpClient([
'base_uri' => 'https://myexternalservice.com/apps/authentication/2/',
]);
// Attempt to send to request with the session ID from the parameters.
try {
$response = $client->request('GET', 'api/v1/user/' . $request->get('session_id'));
} catch (\Exception $e) {
throw new \HttpException($e->getMessage());
}
// Convert the response to an array.
$result = json_decode((string) $response->getBody(), true);
// Convert our array to a user entity.
if ($user = $this->convertResponseToUser($result['user'])) {
try {
// Attempt to load the user. If the user does not exist then create them first.
if (!$assumeUser = user_load_by_mail($user->getEmail())) {
// Create a Drupal user object.
$assumeUser = $this->createUser([
'name' => $user->getFirstName() . ' ' . $user->getLastName(),
'mail' => $user->getEmail()
]);
$assumeUser->save();
}
// Authenticate the user.
user_login_finalize($assumeUser);
} catch (\Exception $e) {
drupal_set_message(t('An unhandled exception occurred during authentication.'), 'error');
return $this->redirect('user.login');
}
}
return $this->redirect('mymodule.route');
}
EDIT Originally I thought Oauth2 is the way to go but maybe it is not. I'll leave that out of this question for now as it is confusing things.
I'm creating a mobile app (Android/iOS). I'd like the user to enter their credentials (user/pass) in the mobile device which would then get sent to my server (Joomla CMS) to verify the credentials and create/send a token. I don't want to store the user/pass on the device just the token.
In addition this token needs to have a timeout to be refreshed when needed. Such as credentials have changed.
At this point I'm trying to figure out what the architecture of this will look like.
Are there any tutorials on how you can achieve this (ideally with Joomla)? Anything that someone could point me to?
You should post the username and password from the mobile app and from there on you should follow the solution provided in this question:
https://stackoverflow.com/a/2188969/900617
The end solution is to create my own Joomla component. Pretty much everything is in my controller. Not the final code but something like this will work.
defined('_JEXEC') or die;
jimport('joomla.application.component.controller');
class FooauthController extends JController
{
function __construct() {
// params
$jinput = JFactory::getApplication()->input;
$this->username = $jinput->get('user', '', 'STRING');
$this->password = $jinput->get('password', '', 'STRING');
$this->checkParameters();
}
private function checkParameters() {
// datatype checks
if ($this->username == '' || $this->password == '') {
header('HTTP/1.1 400 Bad Request', true, 400);
}
}
private function createToken() {
// token generation - what Joomla does (just an example)
jimport('joomla.user.helper');
$salt = JUserHelper::genRandomPassword(32);
$crypted = JUserHelper::getCryptedPassword($password, $salt);
$cpassword = $crypted.':'.$salt;
return $cpassword;
}
function execute() {
// Get the global JAuthentication object
jimport( 'joomla.user.authentication');
$auth = & JAuthentication::getInstance();
$credentials = array( 'username' => $this->username, 'password' => $this->password );
$options = array();
$response = $auth->authenticate($credentials, $options);
// success
if ($response->status === JAUTHENTICATE_STATUS_SUCCESS) {
$response->status = true;
echo json_encode($this->createToken());
} else {
// failed
$response->status = false;
echo json_encode($response);
}
}
}
This represents a component called com_fooauth. Now the native app will send a query like this:
http://www.myhost.com/index.php?option=com_fooauth&user=username&password=pass&format=raw
Kind of a short cut to put everything in the controller, but hopefully you get the idea.
I hope that I understand correctly your use case.
If you want to use oAuth, then your mobile apps are considered as the oAuth-client.
Your "server" holds the "protected resources", and it can be used only with oAuth access-token, so it is called "resource server". Now you want something to supply this access-token, so this is the identity-provider, AKA authentication server, e.g. Facebook, Google, (or implement one by your own).
The flow is (generally): the user (mobile app) tries to reach a protected resource; since it has no token, he is being redirected to the auth-server. the latter is responsible for the user/password login page, and creating the token.
If it is true - you still can implement everything by your own, without using Facebook/Google APIs, because oAuth has SPECs. However, it can be easier for you to use the providers' packages.
EDIT: reconsider the usage of oAuth
You use oAuth only if you want your webapp to support oAuth SPEC. There are several benefits, one of them is that you can use 3rd party identity provider, e.g. Yahoo! and use their identities without managing them. So if I have a user in Yahoo!, I can use your app without additional registrations (your app will have to support access-tokens from Yahoo!). But in your case, you are about to implement all the logic of identity-provider (forgot password, change password, registration, etc) plus supporting oAuth - and all of this without enjoying the benefits of oAuth at all! So - you have to reconsider the usage of oAuth...
You need to use their APIs as a base. They aren't going to just let you build your own API that connects to their database, that to them would look more like a password cracker than an API.
This isn't Joomla or a tutorial, (and I'm very rusty in php) that said...
First a few caveats:
* memcache isn't secure & this implementation has you putting username / password in: Be sure that it is safely behind a firewall, or else encrypt it first. Happy to give some pointers on that if you need it.
* memcache isn't guaranteed not to drop data if it runs out of memory. In practice it is reliable, but your app should handle that gracefully. If you don't want to lose data like that, just substitute something like couchbase for memcache.
* just returning a token in response to a login probably isn't super useful. I'd json-ize the token along with stuff like the user name, and any other info to get the app up and running without needing to make a second API call.
* the code below doesn't handle error cases, I can call them all out in more detail if that isn't obvious to you.
If it were me, I'd just use memcache to persist the tokens & map that token to the username & password that was originally passed. You can use the memcache time to live to get your expiration for free.
Send username / password to the server (ideally over https).
Create a random string or guid (eg: http://php.net/manual/en/function.uniqid.php or http://www.lateralcode.com/creating-a-random-string-with-php/) , this is your token
Store the username / password in memcache with that token as a key
Set a timeout
$token = createToken("user1234", "pass2324");
print "Token: $token \n\n";
$credentials = credtialsFromToken($token);
print "Credentials from the token: ";
var_dump($credentials);
print "\n\n";
function setup() {
$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");
}
function createToken($user, $pass) {
$TOKEN_EXPIRE_TIME=60 * 60 * 24 * 30;
$credentials = array(
"user" => $user,
"pass" => $pass,
);
$token = uniqid( );
memcache_set($token, credentials, 'some variable', 0, 30);
return $token;
}
function credtialsFromToken($token) {
$credentials = memcache_get($token);
return $credentials;
}
If the token is incorrect or expired, they get an null credentials back and have to login.
Edit: cleaned it up into functions that appear to work in php...