I'm creating an API which obviously needs to be accessed through someones client and not a form. But I want to tryout this for myself and don't know how I would go about doing this.
I basically have a page that I only want to show if you're authorised.
Since i'm not too familiar with cURL i'm asking how to do this
It has to require an username and password which are both located in the database. (eg. user:admin, pass:root)
You can do this with a Controller action that checks the credentials in the request, and validates the user without logging him in.
For example, in your routes.php file:
Route::controller('api', 'ApiController');
Your controller (note: I've used anyMyMethod() so that the method is available via GET and POST.. to accept just GET it would be getMyMethod()):
class ApiController extends BaseController {
public function anyMyMethod()
{
$user = array(
'username' => Input::get('user', false),
'password' => Input::get('pass', false)
);
if (Auth::validate($user))
{
// user is valid.. do things!
return "ok";
}
else
{
// user is not valid.. return an auth error.
return "fail";
}
}
}
Now your API is available though the URL: your-domain.com/api/my-method, and you can test it with cURL:
curl -d "user=admin&pass=root" your-domain.com/api/my-method
and there you have it, an API method!
Related
I'm trying to implement matching a Kerberos authentication with a local user database in CakePHP4. So I installed CakePHP 4 and the Authentication plugin 2.0. Since Kerberos auth is managed by our IIS WebServer, only thing I have to do is check if the authenticated user is known by my webapp.
The callback authentication should let me implement something like this, right ?
So I put this function in Application.php :
<?php
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$service = new AuthenticationService();
// Define where users should be redirected to when they are not authenticated
$service->setConfig([
'unauthenticatedRedirect' => '/users/login',
'queryParam' => 'redirect',
]);
// Load the authenticators. Session should be first.
$service->loadAuthenticator('Authentication.Session');
$service->loadIdentifier('Authentication.Callback', [
'callback' => function($data) {
// do identifier logic
if (empty($_SERVER['REMOTE_USER'])) {
return new Result(
null,
Result::FAILURE_OTHER,
['message' => 'Unknown user.']
);
} else {
// On vérifie que l'utilisateur est autorisé à utiliser cette application
$users = TableRegistry::getTableLocator()->get('Users');
$remoteUserNoDomain = str_replace("DOMAIN\\", "", $_SERVER['REMOTE_USER']);
$result = $users->find()
->where(['username' => $remoteUserNoDomain]);
if ($result) {
return new Result($result, Result::SUCCESS);
}
return new Result(
null,
Result::FAILURE_OTHER,
['message' => 'Removed user.']
);
}
return null;
}
]);
return $service;
}
But so far, it doesn't seem to work, like it won't call the callback function at all. I tried to put some debug code, exits... Nothing works.
I would assume that you've also done all the other required configuring for authentication to work, ie loading the plugin, adding the authentication middleware, etc.!?
https://book.cakephp.org/authentication/2/en/index.html
That said, identifiers do not do any work on their own, they are being triggered by authenticators in case they actually require them. You only have the Session authenticator loaded, which in its default configuration doesn't make use of identifiers, but even if you configure it to use identifiers (by setting its identify option to true), it will only use them when there already is an identity in the session, then the identifier is being used to validate that identity.
https://github.com/cakephp/authentication/blob/2.3.0/src/Authenticator/SessionAuthenticator.php#L52
I'm not familiar with Kerberos authentication, but if it pre-populates $_SERVER['REMOTE_USER'] (btw. never access superglobals in CakePHP directly, it will only cause trouble down the road), then what you need is a custom authenticator. You could then re-use the password identifier for the ORM access part, as it allows finding something without checking the password (weirdly enough, given its name).
Quick and dirty example based on your snippet:
// src/Authenticator/KerberosAuthenticator.php
namespace App\Authenticator;
use Authentication\Authenticator\AbstractAuthenticator;
use Authentication\Authenticator\Result;
use Authentication\Authenticator\ResultInterface;
use Psr\Http\Message\ServerRequestInterface;
class KerberosAuthenticator extends AbstractAuthenticator
{
public function authenticate(ServerRequestInterface $request): ResultInterface
{
$server = $request->getServerParams();
if (empty($server['REMOTE_USER'])) {
return new Result(null, Result::FAILURE_CREDENTIALS_MISSING);
}
$remoteUserNoDomain = str_replace("DOMAIN\\", "", $server['REMOTE_USER']);
$user = $this->_identifier->identify(['username' => $remoteUserNoDomain]);
if (empty($user)) {
return new Result(
null,
Result::FAILURE_IDENTITY_NOT_FOUND,
$this->_identifier->getErrors()
);
}
return new Result($user, Result::SUCCESS);
}
}
Your service authenticator/identifier setup would then look like this:
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Kerberos');
$service->loadIdentifier('Authentication.Password');
Nore sure if you'd then really want to use the session authenticator like that though, ie whether you only want to identify the remote user once per session.
I've created a simple test site using CakePHP 3.8 and Authentication 1.0 to try it out. I'd like to use both Form and Basic authentication since the intended app will offer REST calls.
The site works properly if the HttpBasic is not included, that is the Login window is displayed. However, with HttpBasic, the site goes directly to basic authentication.
The code is directly from the cookbook.
What am I missing?
public function getAuthenticationService(ServerRequestInterface $request, ResponseInterface $response)
{
$service = new AuthenticationService();
$service->setConfig([
'unauthenticatedRedirect' => '/users/login',
'queryParam' => 'redirect'
]);
$fields = [
'username' => 'user',
'password' => 'password',
];
// Load Identifiers
$service->loadIdentifier('Authentication.Password', compact('fields'));
// Load the authenticators
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form', [
'fields' => $fields,
'loginUrl' => '/users/login',
]);
$service->loadAuthenticator('Authentication.HttpBasic');
return $service;
}
As mentioned in the comments, using the form authenticator and the HTTP basic authenticator together won't work overly well, this is due to the fact that the authentication service won't stop executing all loaded authenticators, unless one of them returns a response that indicates successful authentication.
This means that you'd always be presented with the authentication challenge response, and never see your login form. Only the actual authentication part would work in that constellation, ie directly sending your login credentials as form data to the login endpoint.
If you don't actually need the basic auth challenge response that is preventing you from accessing the login form, then you could use a custom/extended authenticator that doesn't cause a challenge response to be returned, which should be as simple as overriding \Authentication\Authenticator\HttpBasicAuthenticator::unauthorizedChallenge():
src/Authenticator/ChallengelessHttpBasicAuthenticator.php
namespace App\Authenticator;
use Authentication\Authenticator\HttpBasicAuthenticator;
use Psr\Http\Message\ServerRequestInterface;
class ChallengelessHttpBasicAuthenticator extends HttpBasicAuthenticator
{
public function unauthorizedChallenge(ServerRequestInterface $request)
{
// noop
}
}
$service->loadAuthenticator(\App\Authenticator\ChallengelessHttpBasicAuthenticator::class);
Also not that you might need to add additional checks in case your application uses the authentication component's setIdentity() method, which would cause the identity to be persisted in the session, even when using stateless authenticators. If you don't want that, then you'd need to test whether the successful authenticator is stateless before setting the identity:
$provider = $this->Authentication->getAuthenticationService()->getAuthenticationProvider();
if (!($provider instanceof \Authentication\Authenticator\StatelessInterface))
{
$this->Authentication->setIdentity(/* ... */);
}
I have followed this excellent tutorial Building a Web App with Lumen and OAuth2 for setting up OAuth2 and Lumen. Everything is working fine apart from now I want to access the currently authenticated user information/model.
My route correctly posts the supplied information after I have logged in and I can break with Netbeans inside the controller but I am not clear how to get the user from the underlying Auth framework. I have tried the three methods indicated here Authentication - Laravel but to no avail. The lumen logs shows:
==== routes.php ====
$app->group(['namespace' => 'App\Http\Controllers','prefix' => 'api', 'middleware' => 'oauth'], function($app)
{
$app->post('info', 'InfoController#send');
}
==== InfoController.php ====
namespace App\Http\Controllers;
// the controllers
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Contracts\Auth\Authenticatable;
class InfoController extends Controller{
/* /api/info methods */
public function send(Request $request){
// can iterate over the entire users table but I just
// want the current user (must be some method through
// the authentication stack)
$users = \App\Auth\User::all();
foreach ($users as $user) {
$name = $user->name;
$key = $user->getAuthIdentifier();
$pwd = $user->getAuthPassword();
}
// CODE GETS HERE BUT how to get the current user?
// Authenticated OK (request supplies "Access-Token: Bearer ...")
}
}
This is probably not the cleanest solution and may not match your requirements exactly but it does retrieve the user.
I decided to make another DB query in the proxy to get the user with the same key (in my case, email address) that was requested by the client.
In my case I was sending the user id along with the standard oauth token.
You could use the same technique to set some value in the session.
// ../app/Auth/Proxy.php
namespace App\Auth;
use App\User; // ----- added this line
use GuzzleHttp\Client;
class Proxy {
...
private function proxy($grantType, array $data = [])
{
...
$response = json_decode($guzzleResponse->getBody());
if (property_exists($response, "access_token")) {
...
// added the following line to get the user
$user = User::where('email',$data['username'])->get()->first();
// untested, but you could add the user to your session here
$request = app()->make('request');
$request->session()->put('current_user', $user);
$response = [
'accessToken' => $response->access_token,
'accessTokenExpiration' => $response->expires_in,
'userId' => $user->id,
];
}
...
I'm working with Symfony2 and FOSOauthServerBundle in a REST API. I would wish that some user could log in by a client app using their Google Account, for instance.
From my REST server, by web, I can log in with my Google Account (using HWIOauthBundle), but I need to send to the client app an access_token (like FOSOauthServerBundle does).
I'm interested on persist the access_token that Google send to me in my data base and at the same time, send to the client app the json message {'access_token': 'XMekfmns.... } with Google's (and now my REST API too) access_token.
I don't know if my approach is right. Any ideas?
(sorry for my english ;-) )
Thank you very much
This is my (dirty) solution, but I hope people understand what I want to.
From the client side, the user send the request to get authorization from the google account (I'm using Symfony2 with HWIOauthBundle)
http://myserver.com/connect/google
This present the Google login form. The user fill the form and submit it. If success exists, there will be a redirect to myserver.com where the user will logged.
I catch the event onAuthenticationSuccess ...
<?php
namespace App\Bundle\Handler;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\HttpFoundation\Request,
Symfony\Component\HttpFoundation\RedirectResponse,
Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Router;
class SecurityHandler implements AuthenticationSuccessHandlerInterface
{
private $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$user = $token->getUser();
return new RedirectResponse($this->router->generate('api_get_token', array(
'clientRandomId' => '5ewv02jcis08wsgggk4wow4so0gokco0g4s8kkoc4so4s0gw4c'
)));
}
#clientRandomId value is an existing value in the table (entity) Client ...
#... in the database.
}
... to redirect to a Controller where it will generate an access_token and refresh_token, where they will be saved in the database. At the end, it will be sent a json response to the user, like FOSOauthServerBundle does.
<?php
namespace App\Bundle\Controller\Api;
use Symfony\Bundle\FrameworkBundle\Controller\Controller,
Symfony\Component\HttpFoundation\JsonResponse,
Symfony\Component\Security\Core\Exception\AccessDeniedException;
use FOS\RestBundle\Controller\FOSRestController,
FOS\RestBundle\Controller\Annotations\Route,
FOS\RestBundle\Controller\Annotations\NamePrefix,
FOS\RestBundle\Controller\Annotations\Prefix;
use FOS\RestBundle\Routing\ClassResourceInterface;
use App\Bundle\Entity\AccessToken;
use App\Bundle\Entity\RefreshToken;
class TokenController extends FOSRestController implements ClassResourceInterface {
#this method has the route named 'api_get_token'
public function getAction($clientRandomId)
{
$user = $this->get('security.context')->getToken()->getUser();
#control if user exist ...
#We force the loggout
$this->container->get('security.context')->setToken(null);
$em = $this->get('doctrine')->getManager();
$client = $em->getRepository('AppBundle:Client')->findOneBy(array('randomId' => $clientRandomId));
#control if client exist ...
$expiresAt = time() + 3600;
$accessToken = new AccessToken;
$accessToken->setClient($client);
$accessToken->setToken('access_token'); #This is only an example
$accessToken->setExpiresAt($expiresAt);
$accessToken->setUser($user);
$refreshToken = new RefreshToken;
$refreshToken->setClient($client);
$refreshToken->setToken('refresh_token'); #This is only an example
$refreshToken->setExpiresAt($expiresAt);
$refreshToken->setUser($user);
$em->persist($accessToken);
$em->persist($refreshToken);
$em->flush();
$jsonData = array(
'access_token' => $accessToken->getToken(),
'expires_in' => 3600,
'token_type' => 'bearer',
'scope' => null,
'refresh_token' => $refreshToken->getToken()
);
$response = new JsonResponse($jsonData);
return $response;
}}
I know this is not the best solution but maybe it guide you to a better solution.
I'm writing some REST api for my cake 3.0 application, and I need to set $this->Auth->unauthorizedRedirect to false, as the manual says that this would prevent my application to redirect to login url for unauthorized requests.
http://api.cakephp.org/3.0/class-Cake.Auth.BasicAuthenticate.html
The problem is that I'm trying to set it in my Users controller, and it doesn't work:
class UsersController extends AppController {
public function initialize() {
parent::initialize();
$this->loadComponent('RequestHandler');
}
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
$this->Auth->allow(['logout']);
// Change the authentication mode when using REST api
if(! $this->RequestHandler->accepts('html')) {
$this->Auth->unauthorizedRedirect = false;
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
}
}
}
This scripts works fine as detecting if a user is actually registered, but fails when I try to use wrong authentication data, showing the login form instead of throwing an error. What am I doing wrong?
Authentication and authorization are two different things
You are mixing up authentication and authorization, that's two different things. Logging in a user is authentication, testing whether a logged in user is allowed to access a specific action is authorization.
So the unauthorized redirect configuration applies to logged in users when accessing actions.
Handling unauthenticated requests
What you are looking for, ie throw an exception on unauthenticated requests, is done by the basic authentication adapter by default, so I assume that you actually aren't using this adapter!?
So if you are using a different adapter, this behavior is best implemented in either your controller where you are trying to identify the user
$user = $this->Auth->identify();
if (!$user) {
throw new ForbiddenException('Stop! Hammer time!');
} else {
$this->Auth->setUser($user);
}
or, in case you want the exception to be thrown for every controller, in a custom authentication adapters unauthorized() method, which is being invoked on unauthenticated requests before executing possible redirects. Quote from the docs:
Cookbook > Authentication > Handling Unauthenticated Requests
When an unauthenticated user tries to access a protected page first the unauthenticated() method of the last authenticator in the chain is called. The authenticate object can handle sending response or redirection by returning a response object, to indicate no further action is necessary. Due to this, the order in which you specify the authentication provider in authenticate config matters.
If authenticator returns null, AuthComponent redirects user to login action. [...]
Here's a simple example that extends the form authentication handler:
src/Auth/MyCustomAuthenticate.php
namespace App\Auth;
use Cake\Auth\FormAuthenticate;
use Cake\Network\Exception\ForbiddenException;
use Cake\Network\Request;
use Cake\Network\Response;
class MyCustomAuthenticate extends FormAuthenticate
{
public function unauthenticated(Request $request, Response $response)
{
if(!$request->accepts('text/html')) {
throw new ForbiddenException('Ah ah ah! You didn\'t say the magic word!');
}
}
}
Controller
$this->loadComponent('Auth', [
'authenticate' => [
'MyCustom'
]
]);
See also
Cookbook > Authentication > Creating Custom Authentication Objects
Cookbook > Authentication > Using Custom Authentication Objects