I want to create social login using Google and Facebook. In the first step, I want to create Google Login. When I select the Google ID and Callback it shows me this error.
Laravel\Socialite\Two\InvalidStateException
http://localhost:8000/customer/login/google/callback?authuser=1&code=4%2F0AX4XfWgtwfNmX20WgsbTWXCO0joa1BrfjX0Iif1jqoxTqvT-AqwRVRgwyJwR95EuFlBx9Q&prompt=consent&scope=email%20profile%20openid%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&state=e7PCC5OnNvtkQ6e7GBquKsTtAjWYiIuMdFZ477Kt
URL :
Route::get('/customer/login/google/callback', 'handleGoogleCallback')->name('callback.to.google');
Controller :
public function handleGoogleCallback() {
try {
$user = Socialite::driver('google')->user();
dd($user);
} catch (\Throwable $th) {
throw $th;
}
}
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => 'http://localhost:8000/customer/login/google/callback',
],
GOOGLE_CLIENT_ID="xxxxxxxxxxxxx-xxxxxxxxxxxxxx.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="GOCSPX-qzx7jVkboSS_xxxxxxxxxxxxx"
How to solve this problem??
Your state is incorrect. state is a parameter that laravel-socialite added to auth request once you did /redirect. Seems like you trying complete auth operation two+ times from just only google.com request. Retry auth from /redirect request.
PS. sorry for my English
Related
I am building a simple REST API package using cURL and would like to catch an error and then return a view. I am able to throw an error if I dd($e) but if I try and return a view it just continues with the code after the catch function. Shouldn't PHP kill the process and just go to the login view?
try{
$response = Http::timeout(2)->asForm()->post('https://' . $this->ip_address, [
'username' => $this->username,
'password' => $this->password
]);
} catch(\Illuminate\Http\Client\ConnectionException $e) {
return view('auth.login');
}
If I get a cURL timeout exception I just want to go back to the login page for now. If I put in a bogus IP address obviously it will timeout after 2 seconds, which is what I am testing.
Using Laravel Http client, how can I catch that error and display the auth login view?
Unlike Guzzle, Laravel's HttpClient does not throw errors if the response is > 400.
You should simply use an if statement to check the response status code. See: https://laravel.com/docs/8.x/http-client#error-handling
You can call use the following checks:
// Determine if the status code is >= 200 and < 300...
$response->successful();
// Determine if the status code is >= 400...
$response->failed();
// Determine if the response has a 400 level status code...
$response->clientError();
// Determine if the response has a 500 level status code...
$response->serverError();
So in your case you can simply do something like this:
$response = Http::timeout(2)->asForm()->post('https://' . $this->ip_address, [
'username' => $this->username,
'password' => $this->password
]);
if ($response->failed()) {
return view('your-view')->with([
'message' => 'Failed.',
]);
}
Could you try this please?
try {
$response = Http::timeout(2)->asForm()->post('https://' . $this->ip_address, [
'username' => $this->username,
'password' => $this->password
]);
} catch(\Illuminate\Http\Client\ConnectionException $e) {
return view('auth.login')->with('errorMessage', $e->getMessage());
}
And you can show the error on the frontend, like below;
#if(!empty($errorMessage))
<div class="alert alert-danger"> {{ $errorMessage }}</div>
#endif
It is better if you change your approach in using Laravel's HTTP client,
Move time-intensive tasks - like Http requests - to a job, and run that job in a background queue.
Then, for example, if you wanna use that Http request for authentication, handle your auth logic on that job, don't forget to log and handle exceptions.
afterward, protect your private routes using middlewares and make redirect unauthenticated users to your login page in that middleware.
I hope I could be clear
I have a Laravel application that uses Passport authentication.
Login
public function authenticate(Request $request)
{
$params = [
'grant_type' => 'password',
'client_id' => 1,
'client_secret' => "secret",
'username' => request('username'),
'password' => request('password'),
'active' => 1,
'scope' => '*'
];
$request->request->add($params);
// verify the credentials and create a token for the user
$proxy = Request::create('oauth/token', 'POST');
return Route::dispatch($proxy);
}
I have settled the expiration on AuthServiceProvider:
Passport::routes(function ($router) {
$router->forAccessTokens();
});
Passport::tokensExpireIn(now()->addMinute(1));
Passport::refreshTokensExpireIn(now()->addDays(30));
It works but after 1 minute the token expires. I want a different expiration date for token depending on where I'm trying to make login because I have a website, desktop app and an Android app.
For example:
web app: 8 hours
desktop app: 1 Year
android app: 5 months
I was thinking send me from where I'm trying to make the login, but is that a good way? Are there any other possible ways to do it?
For now I have tried this:
-) deleted From AuthServiceProvider:
Passport::tokensExpireIn(now()->addMinute(1));
And added in Login function:
if (request('from') == 'something') {
Passport::tokensExpireIn(now()->addYears(1));
} else {
Passport::tokensExpireIn(now()->addHours(8));
}
$proxy = Request::create('oauth/token', 'POST');
You can get help from below link please find
For getting user agent detail and apply condition base on agent
for example you can use code like below
if ( Agent::isDesktop() ) {
Passport::tokensExpireIn(now()->addYears(1));
} else if(Agent::isMobile()){
Passport::tokensExpireIn(now()->addMonth(5));
}else{
Passport::tokensExpireIn(now()->addHours(8));
}
I'm currently trying to implement a way to synchronize my PHP App calendar with the Outlook calendar of my clients, using Azure API.
I use OAuth2 and the custom Microsoft provider by Steven Maguire.
I currently run in an issue where I get an error in my response :
{"error":"unsupported_grant_type","error_description":"The provided value for the input parameter 'grant_type' is not valid. Expected values are the following: 'authorization_code', 'refresh_token'."}
I'm having trouble understanding why the grant_type password is not supported, even though it says on the documentation of Azure that it is.
The request looks like this :
client_id=44bef79b-**********************&client_secret=H****************&redirect_uri=https%3A%2F%2F192.168.1.123%2Fmapeyral%2Fcalendarsync.php&grant_type=password&username=******************&password=***********&scope=openid%20profile%20offline_access%20Calendars.ReadWrite
The Authorize url used is : https://login.live.com/oauth20_token.srf
as defined in the Steven Maguire provider.
The header contains the content-type application/x-www-form-urlencoded (I've seen a lot of post where this was what caused the error).
Some of my code :
$this->provider = new Microsoft([
'clientId' => MicrosoftGraphConstants::CLIENT_ID,
'clientSecret' => MicrosoftGraphConstants::CLIENT_SECRET,
'redirectUri' => MicrosoftGraphConstants::REDIRECT_URI,
'urlAuthorize' => MicrosoftGraphConstants::AUTHORITY_URL . MicrosoftGraphConstants::AUTHORIZE_ENDPOINT,
'urlAccessToken' => MicrosoftGraphConstants::AUTHORITY_URL . MicrosoftGraphConstants::TOKEN_ENDPOINT,
'urlResourceOwnerDetails' => MicrosoftGraphConstants::RESOURCE_ID,
'scope' => MicrosoftGraphConstants::SCOPES
]);
if ($_SERVER['REQUEST_METHOD'] === 'GET' && !isset($_GET['code']))
{
// Try getting access token from Database
$workingAccount = $GLOBALS['AppUI']->getState('working_account');
if (isset($workingAccount))
{
// DB access
$DB = new DatabaseConnection();
$dbAccess = $DB->getConnection();
$contactData = DBUserUtils::getContactDataFromEmail($GLOBALS['AppUI']->getState('working_account'), $dbAccess);
// If at least one user contact found
if (!is_null($contactData))
{
// If has refresh token => fill session variables using refresh token
if (!is_null($contactData['contact_refreshToken']))
{
log_msg('debug.log', 'Has refresh token');
$GLOBALS['AppUI']->setState('preferred_username', $contactData['contact_email']);
$GLOBALS['AppUI']->setState('given_name', $contactData['contact_first_name']." ".$contactData['contact_last_name']);
// Get new tokens
$newAccessToken = $this->provider->getAccessToken('refresh_token', [
'refresh_token' => $contactData['contact_refreshToken']
]);
// Update tokens and DB
$GLOBALS['AppUI']->setState('refresh_token', $newAccessToken->getRefreshToken());
$GLOBALS['AppUI']->setState('access_token', $newAccessToken->getToken());
DBOAuthUtils::updateTokenForUser($contactData['contact_id'], $GLOBALS['AppUI']->getState('refresh_token'), $dbAccess);
$this->redirectTo($redirectURL);
}
else
{
$this->getAccessToken();
}
}
else
{
$this->getAccessToken();
}
}
else
{
$this->getAccessToken();
}
function getAccessToken(){
$accessToken = $this->provider->getAccessToken('password', [
'username' => '*************',
'password' => '********',
'scope' => MicrosoftGraphConstants::SCOPES
]);
}
During the first try it doesn't pass the if (isset($workingAccount)) condition (as expected) and go straight to the last else.
Code is a bit ugly for now but I don't think it has an impact on my problem.
Any help would be appreciated !
Thanks
Edit : added code
That helped me, the problem was that I need to use Azure Active Directory and not Azure AD 2.0.
Problem solved !
So, I'm using Laravel+Passport and so far is working fine.
But, I would like to made a small change to the passport code(well, not in the vendor folder, I hope), once that I would request the User to change it's password in case that he is doing the first login.
So, what I would need is two things (I believe):
1 - How can I add one more info to the oauth/token response? Together with the access_token, I would like to add one column from the DB that is needsNewPassword=true/false.
2 - In case that needsNewPassword is true, then, the app will redirect to another screen, where the user will set a new password. I would set the new password, remove the flag for needsNewPassword and send back a new access_token to the user. The user then, would use only that access_token. How can I regenerate a new access_token?
Thanks for you help! João
Right,
I answering my own question, in case someone needs to do the same. Maybe is not the best way, but is working.What I did is:
Create a new route, like /api/login that points to a method (be sure that is Outside of your middleware "auth", once that it's not sending the token in thi call). E.g: Route::post('/login', 'Auth\LoginController#apiLogin');
in the method, you do a request to the oauth/token and, with the result, you add the fields that you want.
test
function apiLogin(Request $request) {
$tokenRequest = $request->create('/oauth/token', 'POST', $request->all());
$request->request->add([
"client_id" => 'your_client_id',
"client_secret" => 'your_client_secret',
"grant_type" => 'password',
"code" => '*',
]);
$response = Route::dispatch($tokenRequest);
$json = (array) json_decode($response->getContent());
$json['new_value'] = '123456';
$response->setContent(json_encode($json));
return $response
}
This is working for me. In my case, I also have just one app so, my client_id, client_secret, grant_type and code is added in the server side. The client only need to pass username(or email, depends of what you are using) and password and then it will get the access_token and the other info that I want to send as well.
Hope that this helps someone else too.
Cheers,
joao
#joao.sauer
Your own answer is working like a charm, but if you wan't a bit more freedom, you could extend Passport's own AccessTokenController.
A simple example:
use App\Models\User;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Psr\Http\Message\ServerRequestInterface;
use Response;
class AccessTokenController extends \Laravel\Passport\Http\Controllers\AccessTokenController
{
public function issueToken(ServerRequestInterface $request)
{
try {
//get username (default is :email)
$username = $request->getParsedBody()['username'];
//get user
$user = User::where('email', '=', $username)->firstOrFail();
//issuetoken
$tokenResponse = parent::issueToken($request);
//convert response to json string
$content = $tokenResponse->getBody()->__toString();
//convert json to array
$data = json_decode($content, true);
if(isset($data["error"]))
throw new OAuthServerException('The user credentials were incorrect.', 6, 'invalid_credentials', 401);
//add access token to user
$user = collect($user);
$user->put('access_token', $data['access_token']);
return Response::json(array($user));
}
catch (ModelNotFoundException $e) { // email notfound
//return error message
}
catch (OAuthServerException $e) { //password not correct..token not granted
//return error message
}
catch (Exception $e) {
////return error message
}
}
}
credits to Messi89:
Laravel Passport - Customize The Token Response
I found a simple solution without need new request, controller or extends, just add parameters to request and call issueToken via app, it can useful for starter:
// in routes/api.php
Route::post('/token',function(Request $request){
$request->request->add([
'grant_type' => 'password',
'client_id' => '2',
'client_secret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
]);
return app()->call('\Laravel\Passport\Http\Controllers\AccessTokenController#issueToken');
});
Also can add try...catch block to handle exceptions or add parameters to response before send to client
Route::post('/token',function(Request $request){
$request->request->add([
'grant_type' => 'password',
'client_id' => '2',
'client_secret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
]);
try {
$response = app()->call('\Laravel\Passport\Http\Controllers\AccessTokenController#issueToken');
$newResponse = json_decode($response->content());
// Add parameters to response here
$newResponse->user = ['user'=>'user','pass'=>'pass'];
return Response()->json($newResponse);
}catch (Laravel\Passport\Exceptions\OAuthServerException $e) {
if ($e->statusCode() == 400) {
return response()->json(['message' => 'Invalid request. Please enter username and password.'], $e->statusCode());
} else if ($e->statusCode() == 401) {
return response()->json(['message' => 'Your credentials are incorrect. Please try again.'], $e->statusCode());
}
return response()->json('Something went wrong on the server. Please try later.', $e->statusCode());
}
});
I have installed laravel 5.3 and passport pakage.
I followed the documentaition step by step
I can use the following route POST /oauth/token with the following parameters
username
password
client_secret
grant_type
client_id
and I get the following response
{
"token_type": "Bearer",
"expires_in": 31536000,
"access_token": "access token here",
"refresh_token": "refresh token here"
}
then I request GET /api/user
with the following header
Authorization = "Bearer access token here"
accept = application/json (optional)
and this is work fine and so all apis.
the problem I have is the user who I authinticated and entered his username and password in the first request and return me back the access token is a user I have created from laravel web view /register
How can I create new user or register new user from the api route file
like POST /api/register
the user at first time need to register to be authinticated after that.
Should I create this route without oauth to register then if success the registration he request POST /oauth/token to be authinticated or what?
Am I missing something ??
update
the clent_secret is it right to be constant in all users requests or each user should have diffrent clent_secret, and if it is how to create aclent secret if it neaded to authinticate user ?
The fastest way to do this is adding an exception to your verifyCsrfToken.php class
protected $except = [
'register'
];
Then you can post to your register model, and afterwards accessing this account with oauth/token.
If i understand your question correctly, you want to register new user and get token after registration from /oauth/token. For this you can use a proxy. I have used something similar and i followed the following steps. This works for both Login and Register
Register the user and send a HTTP request to /oauth/token endpoint from your register method
public function register(RegisterRequest $request)
{
$user = User::create($request->all());
$response = $this->authenticationProxy->attemptLogin($request->email, $request->password);
return response()->json(compact('response', 'user'), 201);
}
Then in your proxy class, call the /oauth/token
public function attemptLogin($email, $password)
{
$http = new GuzzleHttp\Client;
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => env('CLIENT_ID'),
'client_secret' => env('CLIENT_SECRET'),
'username' => $email,
'password' => $password,
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
}
Hope this helps. You can use similar approach for login as well.