I am developing with CakePHP (2.5.6) and my goal is to use both form and basic auth.
I need this because I call some rest actions from a android app with basic auth. If the user visits the website with a browser I want to use form auth.
I set up my AppController to support both and this works fine. But if I access an action that requires authentication the basic auth alert pops up.
The best way would be to redirect to users -> login. But the basic auth alert pops up first.
How can I get around this? Is there a better solution?
I solved the problem by my own.
I have added a route prefix for api calls.
Configure::write('Routing.prefixes', array('api'));
in core.php file.
Now i can trigger api calls in the AppController's beforeFilter and add Basic authentication only for the api calls.
public function beforeFilter()
{
if(isset($this->request->params['api']))
{
// api call
// Pass settings in
$this->Auth->authenticate = array(
'Basic' => array('userModel' => 'User')
);
}
}
Example action.
PostsController -> index
public function api_index {
}
Url: domain.de/api/posts/index.json
Related
I have a project in Laravel where I am using the same method for both a web and api route. So for example i would have the following:
//routes/web.php
Route::post("/import-results", "ImportController#doImport");
//routes/api.php
Route::post("/import-results/{auto}", "ImportController#doImport");
So regardless of the route the doImport() function will be called in the ImportContoller.
The import controller has a function like this (there is validation in the function but I have taken it out for simplicity):
public function doImport($auto = null){
$importData = new DataImport();
$importData->type = $_POST['type'];
$importData->data = $_POST['data'];
$importData->user = Auth::user()->name;
$importData->save();
$data = [
"success" => true,
"message" => "Message Here!"
]
if($auto){
return json_encode($data);
}else{
return view('import-message', $data);
}
}
As you can see this method uses Auth::user()->name; to identify which user imported the data. This is fine if i am logging in and using a regular web route but what about if i'm using an API and using basic auth where no sessions are created and I don't want sessions to persist if the api routes are called.
How do i get the user info when calling API routes?
Also for the web routes i have customised my login as i'm using ldap but essentially the login happens by doing $this->guard()->login($user, false); in a class with the AuthenticatesUsers trait.
I could do this for my API routes too but does this creates a session and how do i clear this session once the request has ended? Or is there a better way??
To make session work for both page submitting and api. You need work in web.php
Make both route in web.php
//Web.php
Route::post("/import-results", "ImportController#doImport");
Route::post("/api/import-results/{auto}", "ImportController#doImport");
api.php is stateless mean no session exist in api.php . It's working with token based like JWT.
I am using CakePHP as a REST API for a single-page app.
Every request gets authenticated and authorized before proceeding.
The problem is, on logging in, if the credentials are wrong, Cake returns 401 and the browser shows its own server log in a popup.
I believe there is a way to stop it by unsetting the WWW-authenticate header, but I need to know how. Can someone explain how to unset that header?
The headers are being set in the \Cake\Auth\BasicAuthenticate authentication adapter.
https://github.com/cakephp/cakephp/blob/3.0.11/src/Auth/BasicAuthenticate.php#L85-L110
It's hardcoded, so if you want to change this behavior, you'll have to create a custom/extended authentication adapter and override this behavior.
Here's a quick example:
src/Auth/MyCustomBasicAuthenticate.php
namespace App\Auth;
use Cake\Auth\BasicAuthenticate;
use Cake\Network\Exception\UnauthorizedException;
use Cake\Network\Request;
use Cake\Network\Response;
class MyCustomBasicAuthenticate extends BasicAuthenticate
{
public function unauthenticated(Request $request, Response $response)
{
throw new UnauthorizedException();
}
}
Controller
$this->loadComponent('Auth', [
'authenticate' => [
'MyCustomBasic'
]
]);
See also
Cookbook > Authentication > Creating Custom Authentication Objects
Cookbook > Authentication > Using Custom Authentication Objects
I have implemented findIdentityByAccessToken in my Users model.
public static function findIdentityByAccessToken($token, $type = null)
{
$apiUser = ApiAccess::find()
->where(['access_token' => $token])
->one();
return self::findOne(['id' => $apiUser->idUser]);
}
In the browser, if i'm logged into the system, I can hit an api get endpoint, enter my auth token and be authenticated properly.
However, If i am not logged in, I get kicked back to my login screen. Using a rest client, I am returned the HTML of the login screen.
This indicates 1 of 2 things in my eyes. Either, in the current state, it is requiring a 'logged in session' in order to access that api module. Or #2, I'm not properly passing the auth token.
My Request header:
Accept: */*
Cache-Control: no-cache
Authentication: Basic base64('mytoken':)
How do I override my "default" login behavior? OR Properly send the authentication token?
You can override login method and loginByAccessToken from model User to change the login behavior. See: http://www.yiiframework.com/doc-2.0/yii-web-user.html
On the other hand, what you probably need (in case that you don't have it yet) is to write a controller and implement a login action. Then implement a class extending from AuthMethod and authenticate method (and maybe challenge method). After that you can add that class as a behavior to all your controllers (or even better make all your controller inherit from one controller with that behavior).
Plase take a look at this link: http://www.yiiframework.com/doc-2.0/guide-rest-authentication.html
My CakePHP v2.4.X app supports both Basic and Form authentication (Form is for web users, and Basic is for Stateless access from Android App).
AppController.php contains the following $components declaration:
public $components = array(
'Auth' => array(
'authenticate' => array(
'Basic',
'Form',
),
),
);
From the doc on performing stateless Basic Auth:
"In your login function just call $this->Auth->login() without any checks for POST data."
My issue is that if the user logs in using Basic Auth, they never trigger Users/login - so I am unsure where to place the $this->Auth->login() function.
Do I simply place this code in AppController/beforeFilter() and if the current user is not logged in I attempt login every time? ie:
if($this->Auth->loggedIn() == false)
{
$this->Auth->login();
}
This doesn't seem right to me because if the user is using Form login they'll end up calling $this->Auth->login(); twice [once from AppController/beforeFilter(), and again from UsersController/login()].
Also, when simply loading the login (via GET), the system will attempt to log them in and therefore return an error message.
I am also unsure how to determine if the user did login via Basic (as opposed to Form), and therefore set: "AuthComponent::$sessionKey" to false only when Basic was used.
Any help would be much appreciated.
The manual section related to basic auth doesn't correspond to what you are saying. Since 2.4 basic/digest auth doesn't need a login action at all. Just including Basic in the array for "authenticate" key for auth config is enough. It will automatically cause cake to check for required credentials when trying to access a protected action and if no credential or invalid credentials are provided appropriate headers are returned to the client.
Using both Basic and Form authenticators together can be problematic. I suggest modifying auth config in beforeFilter to use only either one of them conditionally by using appropriate conditions to check if request is from mobile app or not.
I am having trouble finding a method for authenticating API users in my application. I set out to initially build a system that users could access and authenticate through the web, but requirements have changed and I need to implement some additional actions that can be available in a RESTful manner using a POST API call.
I have created a class that extends CBehaviour and forced a redirect to the login page for all unauthenticated users (found on the yii framework forum here). Problem is that all API calls are forced through the same logic and any POST requests simply spit out the HTML to the login page.
class ApplicationBehavior extends CBehavior {
private $_owner;
public function events() {
return array(
'onBeginRequest' => 'forceGuestLogin',
);
}
public function forceGuestLogin() {
$owner = $this->getOwner();
if ($owner->user->getIsGuest())
$owner->catchAllRequest = array("site/login");
}
}
How would I go about separating the authentication of API users from the Web users?
I would follow this guide on creating a REST API in Yii. After modifying the config urlManager entries, all of your API requests will use the APIController. You can then place the following code in the beforeAction of your APIController to return nothing if the user is a guest (Or an error message)
protected function beforeAction($event) {
if (Yii::app()->user->isGuest) {
echo "Invalid credentials";
Yii::app()->end();
}
}
Note: The code above works for my purposes because all REST requests are sent via the same browser. (Which is already logged in and has a login cookie)
If you replace that behavior with a new base controller placed in protected/controllers to force login, it will only apply to your pages that require a login and not your APIController. Here is an example of mine:
//Make sure all Controllers which require a login inherit from this
class ControllerLoginRequired extends CController {
public function runAction($action) {
if (Yii::app()->user->isGuest && 'site' != $this->route) {
Yii::app()->user->returnUrl = $this->route;
parent::redirect(array('site/login'));
} else {
parent::runAction($action);
}
}
}
Everything explained will work for REST requests via the same browser in which the user has logged onto Yii. If you will have the need to expose your REST service to consumers that are not a browser logged into Yii, I believe you would have to come up with a custom authentication/token scheme.