spring-social: ProviderSignInController: Why signIn(...) with POST only? - spring

Is there a specific reason why a user sign-in with {providerId} can only be initiated with a POST request (to /signin/{providerId})? Why not with a GET request?

Answering my own question: Maybe because of
/**
* Process the authentication callback when neither the oauth_token or code parameter is given, likely indicating that the user denied authorization with the provider.
* Redirects to application's sign in URL, as set in the signInUrl property.
* #return A RedirectView to the sign in URL
*/
#RequestMapping(value="/{providerId}", method=RequestMethod.GET)
public RedirectView canceledAuthorizationCallback() {
return redirect(signInUrl);
}
Anyway, it would be nice if a sign-in with a GET request would be possible, too (with a different URL or a parameter narrowing the mapping).

Related

Route type delete does not work in Laravel

I have following route and works
Route::post("delete-role", [RoleApiController::class, "Remove"]);
I tested it through postman like this
http://localhost/delete-role?api_token=hcvhjbhjb12khjbjhc876
Now, if I change above route and convert to type delete..
Route::delete("delete-role/{role_id}", [RoleApiController::class, "Remove"]);
it does not work. I get below error. It seems to be the reason that the api_token is missing.
I get same error when trying to update route like below
Route::delete("delete-role/{role_id}/{api_token}", [RoleApiController::class, "Remove"]);
You have to set header of your request as:
"Accept": "application/json"
in postman.
If you don't set the required header for api, Laravel Passport can't understand request as an API client and so it will redirect to a /login page for the web.
Or you can set a middleware to check it in code:
public function handle($request, Closure $next)
{
if(!in_array($request->headers->get('accept'), ['application/json', 'Application/Json']))
return response()->json(['message' => 'Unauthenticated.'], 401);
return $next($request);
}
You have an incomplete details. but I see few issues here.
You seem to be using web routes for your API requests which is a bad set-up
You do not have a route with login name.
based on the error you posted, your request seems to successfully destroyed the token and logged you out, then called the middleware App\Http\Middleware\Authenticate which supposed to redirect your request to login route which does not exist and the reason you are getting that error.
You can see from that Authenticate middleware it will supposed to redirect you to login route for unauthenticated request. thats why you need to use the api routes so you can handle the response manually
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* #param \Illuminate\Http\Request $request
* #return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}
Also, I'm not so sure about this, but the reason you are not getting the same issue with your POST request is probably because your POST request does not call the Authenticate middleware or whatever in your routes file or class function that calls the authenticate middleware.
But again, just use api routes if you don't want un-authenticated request to automatically redirect your request to login routes which does not exist in your application
The problem is that he doesn't define route ('login'),
add in Exceptions/Handler.php
$this->renderable(function (AuthenticationException $e, $request) {
if ($request->is('api/*')) {
return response()->json(['meassge' => 'Unauthenticated']);
}
});
Then you should use Passport Or Sanctum for auth with Api,
Continue from here https://laravel.com/docs/9.x/passport
Probably, this thread could help you. Route [login] not defined
(OR)
You need to setup auth scaffolding to get login route defined.
Important: your project will be overridden if you setup auth scaffold.
So, I would only recommend doing this if it is a new project or a testing app.
See this for detail doc but install Laravel Breeze would be suffice.
It Appears you have called route('login') without having defined it in your routes, Please remove route('login') from your code or define it in your routes. eg::
Route::get('login', [YourController::class, 'methodName'])->name('login');

"GET method is not supported for this route" even though it's a POST route

I have a POST route in my Laravel application:
Route::post('/register-direct', 'Auth\RegisterController#direct')->name('register.direct');
Currently the method doesn't do anything but try to log the request:
public function direct(Request $request) {
logger()->info($request->all());
}
Since this route should be accessible from outside the domain, I disabled CSRF protection for it:
class VerifyCsrfToken extends Middleware
{
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* #var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'register-direct'
];
}
However, the strangest thing is happening. Even though it's a POST route, when I try to send the request from Postman to my remote site, I get the error:
The GET method is not supported for this route. Supported methods: POST.
From what I see, it is doing some sort of redirect to /register-direct as a GET route for some reason. The request never reaches the appropriate controller method (since the logging in the method never happens).
I suspect some middleware is the culprit, but the only middleware on this controller is the guest() middleware. And when I disable this middleware, it doesn't change anything.
Additional info:
there is no other route with the same name or URI
when I send the POST request to localhost (instead of to my remote site) it works correctly
the same error appears in Postman and if I try to submit this request from another domain
I have tried moving the route from web.php to api.php and nothing changed
I have other routes in api.php that accept remote requests and work just fine

Why do _token and XSRF-TOKEN differ in Laravel?

I don't understand why is the token for AJAX requests (XSRF-TOKEN) different from a _token that normal forms use. In addition, it's much longer. Why? And why have 2 tokens at all? Why not just use one which would be same for both ajax and normal requests?
1 Approach, 2 Technics
Laravel Uses 2 distinct Technics to prevent CSRF Attack.
The Approaches are The same:
to send a token (CSRF or XSRF) to The Client and Client Have to return
it back in following request
and there are 2 steps:
server sends token (get a form) (CSRF or XSRF)
client return token as X-token (post a form) (X-CSRF or X-XSRF)
when you see an X- token its an client-replied that client sends with Post to the server
The Reason we have 2 technics is not these uses different approaches,
its because web application Client-Side Architectures using 2 different Architectures :
old-fashion : server generates pure html and send it to client
Single Page Application : client SPA Framework (like Vue,React,Angular) send and receive data as Json or Xml and create proper Html in Dom
Now CSRF-Protection Technics Adapts with this Two Client-Side Architectures as Below:
+-------------+-----------------+-----------+------------+
| Client Arch | Protection Tech | Get Token | Post Token |
+-------------+-----------------+-----------+------------+
| old-fashion | sync-token | CSRF | X-CSRF |
| SPA | cookie-header | XSRF | X-XSRF |
+-------------+-----------------+-----------+------------+
Mechanism Description
1.Server Generates Token
Laravel make a CSRF Token (40 chars) and store it in session
/**
* Regenerate the CSRF token value.
*
* #return void
*/
public function regenerateToken()
{
$this->put('_token', Str::random(40));
}
After Generating and Storing token in Session, Token Will be Send To Client as CSRF and XSRF
client side will decide to use whatever it wants.
2.Server Sends Token To Client
for the old-fashioned (sync-token technic) client can receive The CSRF Token in two forms with call to csrf_token() helper method in blade:
in form body : <input type='hidden' name='_token' value='{{csrf_token()}}' />
in meta tag that Ajax request can use it in its header
here is how this helper method returns corresponding value:
/**
* Get the CSRF token value.
*
* #return string
*
* #throws \RuntimeException
*/
function csrf_token()
{
$session = app('session');
if (isset($session)) {
return $session->token();
}
throw new RuntimeException('Application session store not set.');
}
for cookie-header (SPA Frameworks) client framework (like Angular) can receive XSRF Token in the Cookie Because:
there is no Html Form generating in the server which server can seed
its hidden input in it. and The Way it can send its token to the
client is sending it with cookie. (This method named XSRF)
/**
* Add the CSRF token to the response cookies.
*
* #param \Illuminate\Http\Request $request
* #param \Symfony\Component\HttpFoundation\Response $response
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function addCookieToResponse($request, $response)
{
$config = config('session');
$response->headers->setCookie(
new Cookie(
'XSRF-TOKEN', $request->session()->token(), $this->availableAt(60 * $config['lifetime']),
$config['path'], $config['domain'], $config['secure'], false, false, $config['same_site'] ?? null
)
);
return $response;
}
Laravel put token in both places since its up to client which method to use, and expect client to response to one of this methods.
3.Client Sends X- Token To Server
In client-side:
old-fashion (X-CSRF):
post token in post data or:
make ajax call like this:
`$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});`
SPA Framework : These Framework put Token as X-XSRF-TOKEN in Post Headers
Server Checks X- Token Vs in-session Token
Now Its Time To Laravel Check For The Token
in VerifyCSRFMiddleware, Laravel Checks if The Request Should Be Check For CSRF Protection Token it Checks :
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*
* #throws \Illuminate\Session\TokenMismatchException
*/
public function handle($request, Closure $next)
{
if (
$this->isReading($request) ||
$this->runningUnitTests() ||
$this->inExceptArray($request) ||
$this->tokensMatch($request) //compares request_token vs session_token
) {
return tap($next($request), function ($response) use ($request) {
if ($this->shouldAddXsrfTokenCookie()) {
$this->addCookieToResponse($request, $response); //add cookie to response
}
});
}
throw new TokenMismatchException('CSRF token mismatch.');
}
Two of lines are in interest:
$this->tokensMatch($request)
and
$this->addCookieToResponse($request, $response);
so there are multiple data in each request that server can put:
html form input _token (40 chars) (CSRF)
html meta header csrf-token (40 chars) (CSRF)
cookie XSRF-TOKEN (224 chars) (XSRF)
and multiple data can client send to server as response to the tokens
post parameter _token (40 chars) (X-CSRF)
http header X-CSRF-TOKEN (40 chars) (X-CSRF)
http header X-XSRF-TOKEN (224 chars) (X-XSRF)
Why in CSRF token are 40 chars and in XSRF are 224 chars ?
We Will get to this a little bit latter
The Http Request Has To Match Token with one of the Above X-Token
/**
* Determine if the session and input CSRF tokens match.
*
* #param \Illuminate\Http\Request $request
* #return bool
*/
protected function tokensMatch($request)
{
$token = $this->getTokenFromRequest($request);// it get token from request
return is_string($request->session()->token()) &&
is_string($token) &&
hash_equals($request->session()->token(), $token); //checks if it is equal to session token or not
}
/**
* Get the CSRF token from the request.
*
* #param \Illuminate\Http\Request $request
* #return string
*/
protected function getTokenFromRequest($request)
{
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');//check sync-token
if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
$token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
}
return $token;
}
first pattern to examine is sync-token, token from client can be in an <input name='_token' /> or it can be in Http Header if requested from an Ajax method call in the client.
the line
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
will check for that and if it can be retrieved, it will return and check via the session_token
but if (! $token is NULL it will check against the cookie-header pattern:
getting $header = $request->header('X-XSRF-TOKEN') from header and decrypt it, if its need decryption
$token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
if it has been encrypted before it has been added to cookie
Cookie Encryption
This is The Reason That XSRF Token Could be 224chars :
Cookie Encryption
and you may disable cookie Encryption and make the XSRF Token 40 chars, Like The CSRF Token
so The Difference was for The Cookie Encryption.
Necessity of Cookie Encryption
But Why Cookie Needs to get Encrypted?? Why XSRF Cookie needs to get Encrypted??
In General, Laravel Store some data on cookies and cookie can be modified by client. because server dose not want modifications on client, it Encrypt Cookies .
this can be config to not to Encrypt CSRF Cookie Since it is not subjected to change by user and its only subjected to be stole by cookie hijacking which encryption is not going to preventing this event.
The only Difference its make is to having to token (unencrypted and encrypted)
for two CSRF Protection methods.
So if attackers can access a cookie-stored (X-XSRF) Token (Since Hijacking > Cookie is much easier to hijacking runtime html and css with XSS )
it cannot be Abuse With sync-token mechanism.
Since CSRF Attack with http-form parameter is easier since html can be in email
or etc While Runnig Js is Less common.
Conclusion
So if a client use old-fashion client architect . the cookie-header technic > ( XSRF stored in Cookie ) wont leave him with a data leak in cookie.
further information on this prevention patterns can be found here:
https://en.wikipedia.org/wiki/Cross-site_request_forgery#Prevention
If you take a look at vendor/laravel/framework/src/Illuminate/Session/Store.php there is a method named regenerateToken which will make the token for the application
/**
* Regenerate the CSRF token value.
*
* #return void
*/
public function regenerateToken()
{
$this->put('_token', Str::random(40));
}
Then the token that you get either from session or JS(which is also from the session), they all have the same 40 char length and the token is just a simple random 40 character which is stored in your session. it's not encrypted or hashed because only you, the user have the access to the session since if I want to CSRF attack from any outside source the source does not have access to the session so basically it does not required to hash or encrypt that 40 length token.
Side note: token is not encrypted by itself, all the sessions are encrypted in laravel by default.
Heres what the documentation says about the XSRF:
https://laravel.com/docs/8.x/csrf#csrf-x-xsrf-token
So basically its just same value of _token encrypted and saved in cookies
The short answer is that XSRF-TOKEN is encrypted CSRF-TOKEN, that's just what it is. Encryption will end up in a much longer string than the original.
The reason it exists to begin with is (as mentioned in the docs):
This cookie is primarily sent as a convenience since some JavaScript frameworks and libraries, like Angular and Axios, automatically place its value in the X-XSRF-TOKEN header on same-origin requests.
This means when using a framework or library that automatically sets the X-XSRF-TOKEN header in a request if the XSRF-TOKEN cookie is present then you won't have to worry about passing along the CSRF token on every request since it is done automatically.
Personally I'm not 100% sure that the statement in the manual is currently accurate or if it's actually useful. Personally I've ended up removing this cookie completely because of a badly configured firewall some of our clients use which strip long cookies, and so far I haven't missed it.

Laravel 7 force user to enter password

I want to know how can I force user to re-enter his password on certain route action?
Let say if user wants to edit product before save process happens he has to input his password and if it was correct then saving is allowed.
Is there any default middleware for password in laravel 7? or should i make myself?
What do you suggest?
Update
I've found this but it's only work on web routes, i need same thing to work on API routes.
Problem with this middleware is that it except sessions which is only available on web middlewares. I tried to add \Illuminate\Session\Middleware\StartSession::class, into api middleware but still getting this error
RuntimeException: Session store not set on request.
Why not just rewrite shouldConfirmPassword() method?
Session is only used to check when was the password confirmed at. If you are requiring password confirmation every time, just rewrite the method to return true:
/**
* Determine if the confirmation timeout has expired.
*
* #param \Illuminate\Http\Request $request
* #return bool
*/
protected function shouldConfirmPassword($request)
{
return true;
//$confirmedAt = time() - $request->session()->get('auth.password_confirmed_at', 0);
//return $confirmedAt > config('auth.password_timeout', 10800);
}
For those who stumble on this, the solution is setting config('password_timeout') to 1 i.e 1 second.
'password_timeout' => 1,
You might think that setting it to 0 should work but it doesn't and that's because of this line in the constructor of the Illuminate\Auth\Middleware\RequirePassword class
$this->passwordTimeout = $passwordTimeout ?: 10800;
which defaults $this->passwordTimeout to 3 hours when config('password_timeout') is not set (null) or 0.
The RequirePassword middleware is being bound to the application container in the registerRequirePassword method of the Illuminate\Auth\AuthServiceProvider class

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