The most simple basic authentication in Yii2 - implementation of IdentityInterface - session

I would like to add HTTP basic authentication to my Yii2 application in which the username/password is stored in the Yii configuration file - for exactly one user, no DB authentication.
I have enabled authentication for my controller by adding behaviors function:
public function behaviors()
{
return [
'basicAuth' => [
'class' => \yii\filters\auth\HttpBasicAuth::className(),
'auth' => function ($username, $password) {
if ($username=='api') {
return new SimpleApiUser();
} else {
return null;
}
},
],
];
}
And I was required to create class, that implements IdentityInterface, that is why I have class:
class SimpleApiUser implements IdentityInterface {
public static function findIdentity($id)
{
return null;
}
public static function findIdentityByAccessToken($token, $type = null)
{
return null;
}
public function getId()
{
return 1;
}
public function getAuthKey()
{
return 1;
}
public function validateAuthKey($authKey)
{
return true;
}
}
That is fine, the application asks for username/password in the case of the first request, but then it managed to store the authentication somehow in some internal session and it does not required repeated authentication for each new request be it made from the browser (which my add sessions) or be it from Postman (which certainly does not keep sessions). Is it possibly to modify user class to require to provide username and password with each new request?

I've tried to play around a bit with your code.
First, your code is enough to require the basic auth to be part of every request. If the Authorization header is not present in request the yii will return 401 Unauthorized. So your implementation is already doing what you need.
There are reasons why user is not required to enter username/password each time.
For web browsers:
The credentials are saved for session and the browser will send them automatically in each subsequent request without prompting them again from user.
For postman: The authorization is stored for request as long as you don't manually remove the authorization settings it will be sent as part of each request.
If you want to force users to manually enter username/password with each request you can extend the HttpBasicAuth component to pretend that the authorization was not part of request for every other request like this
use Yii;
use yii\filters\auth\HttpBasicAuth;
class MyHttpBasicAuth extends HttpBasicAuth
{
const SESSION_KEY = 'authForced';
private $session;
public function __construct($config = [])
{
parent::__construct($config);
$this->session = Yii::$app->session;
}
public function authenticate($user, $request, $response)
{
if ($this->session->has(self::SESSION_KEY)) {
$this->session->remove(self::SESSION_KEY);
return parent::authenticate($user, $request, $response);
}
$this->session->set(self::SESSION_KEY, true);
return null;
}
}
But this will require sessions to work so it can only be used with browsers. It won't work well for API. For postman this implementation would make every other request fail with 401 Unauthorized. It would fail same way for api that will work with cookies and it would fail each request for api that wouldn't work with cookies.
Side note: The postman does keep/send cookies so sessions works with postman.

Related

Test Passport's Authorization code grant authentication flow

Any idea on how i can test my authentication routes in authorization code grant:
- GET: '/oauth/authorize/?' . $query
- POST: 'oauth/token'
The problem is that according to the docs you need to provide a redirect_uri field in your query and i don't know how you suppose to have one in tests and then get the response from your laravel app.
i don't want to test this api with my frontend app.(if possible)
i haven't showed any code bc i just need a general idea of the testing process of such APIs that are working with clients and redirect_uris
on google i found tests around password grant authentication which doesn't need a redirect_uri field
this is what i tryed and it failed.
test:
$user = User::orderBy('id', 'asc')->first();
$token = $user->createToken('personal_access');
Passport::actingAs($user, [], 'api');
(new AuthController)->logout();
if (($user = Auth::user()->toArray()) !== null) {
dd(1, $user);
} else {
dd(0);
}
Auth::user() returns the $user
AuthController:
public function logout(): Response
{
$tokenId = $this->getTokenId();
$tokenRepository = app(TokenRepository::class);
$tokenRepository->revokeAccessToken($tokenId);
$refreshTokenRepository = app(RefreshTokenRepository::class);
$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId);
Artisan::call('passport:purge');
return response('Successfully loged you out.', 200);
}
private function getTokenId(): int
{
return (new CheckAuthentication)->getAuthenticated()->token()->id;
}
$tokenId is always zero.

Codeigniter showing error when I try to resubmit form with csrf_protection set to true

My CI website has csrf protection.
$config['csrf_protection'] = TRUE;
So, when I resubmit form by refresh I am getting the following error.
The action you have requested is not allowed
Instead of showing this message, I want it to return to last page.
So, I try to override csrf_show_error() method by extending the CI_Security file.
This is my class located in application/core/My_Security.php
class MY_Security extends CI_Security {
public function __construct()
{
parent::__construct();
$this->load->library('user_agent');
}
public function csrf_show_error()
{
// show_error('The action you have requested is not allowed.'); // default code
// force page "refresh" - redirect back to itself
// a page refresh restores the CSRF cookie
if ($this->agent->is_referral())
{
redirect(site_url());
} else {
redirect($_SERVER['HTTP_REFERER']);
}
}
}
I am getting the following error
Call to a member function library() on a non-object
Insted of changing the core classes, I extended the MY_Securtiy class in core folder of application. and redirecting to past page.
File Location: application\core\MY_Security.php
class MY_Security extends CI_Security {
public function __construct()
{
parent::__construct();
}
public function csrf_show_error()
{
header('Location: ' . htmlspecialchars($_SERVER['REQUEST_URI']), TRUE, 200);
}
}
Thanks for your solution, but it seems better with a return code 302 by changing the request type of the new request to GET, regardless of the type employed in the original request (e.g. POST). The next refresh will not ask any question.

Laravel 5.3 API route not saving session between requests

I am trying to build a static HTML viewer through Laravel's 5.3 API routing logic and JWT. The files are all stored on S3 and need to be protected so I thought the best way to do this was to make a kind of proxy that all the files pass through. That way I can check the token of the user from the API request and load the files accordingly.
The first file loads fine.
http://example.com/api/proxy/file.html?token={token}
The issue arises when the HTML file tries to load files from itself. It works when I strip out the authentication functions so I know it's not an issue with getting the files. It's because the token is not appended to future requests. It sends this instead without the token.
http://example.com/api/proxy/some_image.png
I attempted to add the following code to my token checker logic.
public function __construct(JWTAuth $jwtAuth)
{
$this->middleware(function ($request, $next) use ($jwtAuth) {
if (!$jwtAuth->getToken()) {
if (!Auth::user()) {
return response()->error('The token could not be parsed from the request', 400);
} else {
$this->authUser = Auth::user();
}
} else {
$this->authUser = $jwtAuth->parseToken()->authenticate();
Auth::setUser($this->authUser);
}
return $next($request);
});
}
But for some reason this does not work. When the first .html loads up with the token it tries to authenticate the user using Laravel's Auth middleware but Auth::user() returns null on the image request.

404 Error when Testing Request class validation through Chrome Postman Extension

Below is my Controller Action method. You can see I passed a param of Request Class to validate before going to save the data in database
public function store(RoleRequest $request)
{
}
My Request class is below.
class RoleRequest extends Request
{
private $Role;
public function __construct(IRole $_role) {
$this->Role = $_role;
}
public function authorize()
{
return true;
}
public function rules()
{
return [
'Role' => 'required|max:20|min:4,
];
}
}
Above code works perfectly when I run it through web page(blade).
I created one more controller for API to Send save request. Below is the code
class RoleApiController extends Controller
{
public function store(RoleRequest $request)
{
}
}
Issue comes, when I type just 1 char for role name and the Request class impose validation and I get 404 error This time I am sending request from Postman Extension in Chrome.
Error Details
The Validation Request class checks if your request is an ajax request or a normal request.
If it's a normal request it does: redirect()->back() with the validation messages in the session.
If it's an ajax request it shows a json object with the validation messages in it.
Frontend frameworks/libraries like for example jQuery add a header to an ajax request to let the backend know it's an ajax request. Laravel checks this header to decide what to do (using the isAjax or wantsJson methods).
Postman does not automatically send this header. So you should add one of the following headers manually:
Accept: application/json
X-Requested-With: XMLHttpRequest
Here is a screenshot of postman as an example:

Can I store an access Cookie in a Laravel session?

I am working with a remote API that is normally accessed directly via JavaScript. In the normal flow, The user authenticates by sending Auth headers and in return is granted a cookie.
What I am trying to do is send auth headers from a laravel app, authenticate in the app controller, and provide API access through laravel controller functions.
I was hoping this would be as simple as authenticating and sending my subsequent API calls, hoping that the cookie given to the PHP server would continue to grant authentication.
Well that doesn't work and thats fine, but now I am thinking that I need to store my access cookie in the Session, and send it in the headers for future API calls.
Will this work/how can I go about this? My supervisors don't want to implement OAuth type tokens on the remote server and to me that seems like the best route, so I am a bit stuck.
Cookies cannot be shared across multiple hosts. The cookie (on the client) is only valid for path which set it.
EDIT - ADDING ADDITION AUTH DETAIL
Setting up remember me in Laravel
When migrating (creating) you User table add $table->rememberToken()
to create that column in your User table.
When user signs up to your service add a check box to allow them to
make the decision OR you can just set it true if you don’t to offer
the user the option as described in step 3
< input type="checkbox" name="remember" >
In your controller you add the following code:
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// The user is being remembered...
}
Users table must include the string remember_token column per 1. , now assuming you have added the token column to your User table you can pass a boolean value as the second argument to the attempt method, which will keep the user authenticated indefinitely, or until they manually logout. i.e. Auth::attempt([$creditentials], true);
Side note: the Illuminate\Contracts\Auth\UserProvider contract, public function updateRememberToken(Authenticatable $user, $token) uses the user’s UID and token stored in the User table to store the session auth.
AUTH ONCE:
Laravel has once method to log a user into the application for a single request. No sessions or cookies. Used with stateless API.
if (Auth::once($credentials)) {
//
}
OTHER NOTES
The remember cookie doesn't get unset automatically when user logs out. However using the cookie as I explained below in cookies example you could add this to your logout function in your controller just before you return the redirect response after logout.
public function logout() {
// your logout code e.g. notfications, DB updates, etc
// Get remember_me cookie name
$rememberCookie = Auth::getRecallerName();
// Forget the cookie
$forgetCookie = Cookie::forget($rememberCookie);
// return response (in the case of json / JS) or redirect below will work
return Redirect::to('/')->withCookie($forgetCookie);
OR you could q$ueue it up for later if you are elsewhere and cannot return a response immediately
Cookie::queue(forgetCookie);
}
Basic general cookie example that might help you. There are better approaches to do this using a Laravel Service provider
// cookie key
private $myCookieKey = 'myAppCookie';
// example of cookie value but can be any string
private $cookieValue = 'myCompany';
// inside of a controller or a protected abstract class in Controller,
// or setup in a service ... etc.
protected function cookieExample(Request $request)
{
// return true if cookie key
if ($request->has($this->myCookieKey)) {
$valueInsideOfCookie = Cookie::get($this->myCookieKey);
// do something with $valueInsideOfCookie
} else {
// queue a cookie with the next response
Cookie::queue($this->myCookieKey, $this->cookieValue);
}
}
public function exampleControllerFunction(Request $request)
{
$this->cookieExample($request);
// rest of function one code
}
public function secondControllerFunction(Request $request)
{
$this->cookieExample($request);
// rest of function two code
}

Resources