Google api php redirect_uri_mismatch - laravel

When using the following composer package bitgandtter/google-api for google php api client since I'm using it in combination with laravel 4 I get the following error redirect_uri_mismatch. My code looks like this(which is located under app/lib using the PSR-0 spec):
class Google {
private $client;
private $token;
public function __construct($code)
{
$this->client = new Google_Client();
$this->client->setApplicationName("camelCaseD");
$this->client->setClientId('SOMENUMBERS.apps.googleusercontent.com');
$this->client->setClientSecret('PRECIOUS');
$this->client->setRedirectUri('http://localhost:9000/auth/google');
$this->client->authenticate($code);
}
}
My routes are:
Route::group(['prefix' => 'auth'], function()
{
Route::post('google', function()
{
$postInput = file_get_contents('php://input');
try
{
$google = new Google($postInput);
} catch (Exception $e)
{
return Redirect::to('signin')->with('error', $e->getMessage());
}
});
});
I'm using the official google plus sign in button to log the user in then passing the authorization code to my server via $.ajax().
Here's what my api console settings look like:

I got that similar error. To resolve mine, I went to google console and reset the secret. I also made sure the Authorized JavaScript origins was set to the correct address.

http:// localhost :900/auth/google
is a directory or a page?
Maybe if it is a directory, the final url is different (like http:// localhost :900/auth/google/index.php) and Google does a control between 2 string, but they are different.

Related

Laravel passport 404, Trying to connect a front end client to Laravel back-end api

Please help explain me a bit. I'm very new to this back-end, OAuth 2.0. I'm trying to make my Laravel project to become an api with Passport authorization system. But after following the passport installation guide from official documents and watching youtube tutorials, I still can't connect Laravel to my front end. Can you please point out what I'm missing ? Sorry if this question is super vague, I don't really understand much what's going on.
How and where do I create a new client for my passport back end api ? Should this code be in my front end (or in laravel?) ?
const data = {
name: 'Client Name',
redirect: 'http://example.com/callback'
};
axios.post('/oauth/clients', data)
.then(response => {
console.log(response.data);
})
.catch (response => {
// List errors on response...
});
In the official documents which talks about issuing access tokens with JSON api, there's this explanation.
However, you will need to pair Passport's JSON API with your own frontend to provide a dashboard for your users to manage their clients. Below, we'll review all of the API endpoints for managing clients. For convenience, we'll use Axios to demonstrate making HTTP requests to the endpoints.
The JSON API is guarded by the web and auth middleware; therefore, it may only be called from your own application. It is not able to be called from an external source.
It didn't explain how I could pair my frontend to this Laravel app. I tried adding this to my front end javascript.
const BASE_URL = 'http://127.0.0.1:8000/api';
**
* Trying Passport JSON api to get all clients for a given user.
*/
const getClients = async () => {
try {
const response = await axios.get(`${BASE_URL}/oauth/clients`);
const clients = response.data;
console.log('Here are your clients', clients);
} catch (errors) {
console.error(errors);
}
}
/**
* Trying to create a new client. Not sure if this is equivalent to linking this external app to the backend api or not
*/
const addClient = async() => {
const data = {
name: 'front end jajaja',
redirect: 'http://www.google.com'
};
const response = await axios.post(`${BASE_URL}/oauth/clients`, data);
console.log(response.data);
}
/**
* main function to test it all
*/
const main = () => {
addClient();
// getClients();
}
main();
but after I ran npm run dev and the main() method is called from the front end, console displayed an error saying
POST http://127.0.0.1:8000/api/oauth/clients 404 (Not Found)
I haven't edit routes/api.php.
<?php
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
And from a tutorial by Andrew, he said he used Laravel breeze to make a mock client, but can I use my super simple front end JavaScript instead ? Can you please explain me, how can I connect my front to the back ? How can I get the authorization code, client secret ??

How could be Laravel Socialite be integrated with a SPA?

I know we have the stateless() and and getTargetUrl() methods, than i am trying this at my controller:
public function socialRedirect($provider){
return Socialite::with($provider)->stateless()->redirect()->getTargetUrl();
}
The problem is that in my SPA front end, i can't make a sucessfull request to the provided url as like in the code below:
async facebookLogin() {
try {
const url = await this.$axios.$get('/auth/social/facebook')
this.$axios.$get(url)
} catch (error){
console.log(error)
}
},
I get CORS related stuff errors:
OPTIONShttps://www.facebook.com/v3.3/dialog/oauth?client_id=240805606930310&redirect_uri=http://localhost:8000/api/auth/social/facebook/callback&scope=email&response_type=code
CORS Missing Allow Origin
What am i doing wrong? I am specially confused since i can access the URL by copying it and pasting in another browser tab, but i can't sucessfully make the AJAX request to it.

Unable to download a file (pdf) from Laravel backend via Vue.js (Axios post)

I have a multi-step form in Vue, I am posting the results once I collect all of the information, to a Laravel controller. This is an authenticated area of the site. I am using Passport. So essentially I have a Vue SPA that is the admin area of a website built within the Laravel 5.7 framework.
Vue file:
axios.post('/api/quotes/finalize', this.wizardModel)
.then(response => {
if (response.data.success) {
//
}
})
.catch(err => {
if (err.response.status == 401) {
window.location.href = '/login';
}
swal.showValidationError(
`Request failed: ${error}`
)
})
The controller gets data and makes a pdf. All of that is working. It then has three actions to look at - email the PDF, email the PDF with CC, and download the PDF.
public function finalizeQuote(Request $request)
{
$data = $request->all();
$data['phone'] = $this->formatTelephone($data['phone']);
$dateStamp = date('Ymdhis', strtotime('now'));
$fileName = 'quote-' . $dateStamp . '.pdf';
$html = View::make('quotes.quote', compact('data'))->render();
$conv = new Converter();
$conv->addPage($html)
->save('storage/' . $fileName);
if ($data['actions']['emailPDF']) {
$message = (new Proposal('storage/' . $fileName))
->onConnection('redis')
->onQueue('emails');
if ($data['actions']['carbonCopy']) {
Mail::to($data['emailAddress'])
->cc(Auth::user()->email)
->queue($message);
} else {
Mail::to($data['emailAddress'])->queue($message);
}
}
if ($data['actions']['downloadPDF']) {
return response()->download(public_path('storage/' . $fileName));
}
}
So in dev tools I see the pdf file in the response. No download occurs in the browser. I am sure I am just missing something fundamental here.
Ajax requests alone cannot invoke a download. You could have a hidden form that posts your data to your api endpoint, probably the quickest solution.
Axios has a built in way to do this easily
axios({
method: 'post',
url: '/api/quotes/finalize',
data: wizardModelFormData,
config: { headers: {'Content-Type': 'multipart/form-data' }}
})
Instead of sending json in the request, you would have to send form data. It is pretty easy to setup a form data object, and a quick google on FormData should be enough.
Another way is to do something like this. Have the endpoint return a download url for your file,
if ($data['actions']['downloadPDF']) {
return response()->json(["download_url" => "/api/download/" . $fileName]);
}
and on the client side, use window.location to set the browser location to this api endpoint that only returns files (in this case /api/download/{fileName}). The browser would then download any file returned from that location you set (if you have the headers setup correctly).
This would mean you would have to implement the /api/download/{fileName} route, but that is where you would put something like this.
return response()->download(public_path('storage/' . $fileName));
Obviously that is just a simple explanation and it doesn't need to be implemented exactly the same way, but it gets the idea across.
Now you would want to make sure that you don't put any sensitive files in storage/ and have some permissions setup.
Axios is used for XHR requests. Typically, downloading files is done through normal GET requests but here you have an XHR post request to download files.
To download files over AJAX, some folks use libraries and some create
a blob link from the response, append it to DOM and then, trigger a
click on the link.
You can refer this gist and SO post for further details.
Personally, I prefer simple non-XHR get requests for file downloads.

Make AJAX call in AngularJS in Cordova App

I have built an app using Ionic Framework, AngularJS, and Cordova. In it, I have made AJAX calls to several php files using $http.get(), which works perfectly in a browser, but not within the app. The app is able to render internet pages through an iframe so the network is working. I have whitelisted my server where the php files reside inside of the app.js file and using . Also, in my php files I've added header('Access-Control-Allow-Origin: *');
Any suggestions on how to get this AJAX call to work?
.controller('StuffCtrl', function($scope, StuffService, LoadingService) {
StuffService.getStuff().then(function(data) {
LoadingService.show();
$scope.stuff = data;
LoadingService.hide();
});
})
.service('StuffService', function($http){
var myStuff;
return {
getStuff: function(){
return $http.get('http://mydomain/stuff.php').then(function(items) {
myStuff = items.data; return myStuff;
});
}
});
});
I had the same error while working on a hybrid app and then I added these lines in my route config function and it started working
$httpProvider.defaults.useXDomain=true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
This is to enable CORS.
More info on the same can be found here
Add a catch handler to get the error :
StuffService.getStuff().then(function(data) {
LoadingService.show();
$scope.stuff = data;
LoadingService.hide();
}).catch(function(error){
$scope.error = error;
});
And pretty display the error :
<pre>{{error|json}}</pre>
This should tell you what is going on.

Restrict Login Email with Google OAuth2.0 to Specific Domain Name

I can't seem to find any documentation on how to restrict the login to my web application (which uses OAuth2.0 and Google APIs) to only accept authentication requests from users with an email on a specific domain name or set of domain names. I would like to whitelist as opposed to blacklist.
Does anyone have suggestions on how to do this, documentation on the officially accepted method of doing so, or an easy, secure work around?
For the record, I do not know any info about the user until they attempt to log in through Google's OAuth authentication. All I receive back is the basic user info and email.
So I've got an answer for you. In the OAuth request you can add hd=example.com and it will restrict authentication to users from that domain (I don't know if you can do multiple domains). You can find hd parameter documented here
I'm using the Google API libraries from here: http://code.google.com/p/google-api-php-client/wiki/OAuth2 so I had to manually edit the /auth/apiOAuth2.php file to this:
public function createAuthUrl($scope) {
$params = array(
'response_type=code',
'redirect_uri=' . urlencode($this->redirectUri),
'client_id=' . urlencode($this->clientId),
'scope=' . urlencode($scope),
'access_type=' . urlencode($this->accessType),
'approval_prompt=' . urlencode($this->approvalPrompt),
'hd=example.com'
);
if (isset($this->state)) {
$params[] = 'state=' . urlencode($this->state);
}
$params = implode('&', $params);
return self::OAUTH2_AUTH_URL . "?$params";
}
I'm still working on this app and found this, which may be the more correct answer to this question. https://developers.google.com/google-apps/profiles/
Client Side:
Using the auth2 init function, you can pass the hosted_domain parameter to restrict the accounts listed on the signin popup to those matching your hosted_domain. You can see this in the documentation here: https://developers.google.com/identity/sign-in/web/reference
Server Side:
Even with a restricted client-side list you will need to verify that the id_token matches the hosted domain you specified. For some implementations this means checking the hd attribute you receive from Google after verifying the token.
Full Stack Example:
Web Code:
gapi.load('auth2', function () {
// init auth2 with your hosted_domain
// only matching accounts will show up in the list or be accepted
var auth2 = gapi.auth2.init({
client_id: "your-client-id.apps.googleusercontent.com",
hosted_domain: 'your-special-domain.example'
});
// setup your signin button
auth2.attachClickHandler(yourButtonElement, {});
// when the current user changes
auth2.currentUser.listen(function (user) {
// if the user is signed in
if (user && user.isSignedIn()) {
// validate the token on your server,
// your server will need to double check that the
// `hd` matches your specified `hosted_domain`;
validateTokenOnYourServer(user.getAuthResponse().id_token)
.then(function () {
console.log('yay');
})
.catch(function (err) {
auth2.then(function() { auth2.signOut(); });
});
}
});
});
Server Code (using googles Node.js library):
If you're not using Node.js you can view other examples here: https://developers.google.com/identity/sign-in/web/backend-auth
const GoogleAuth = require('google-auth-library');
const Auth = new GoogleAuth();
const authData = JSON.parse(fs.readFileSync(your_auth_creds_json_file));
const oauth = new Auth.OAuth2(authData.web.client_id, authData.web.client_secret);
const acceptableISSs = new Set(
['accounts.google.com', 'https://accounts.google.com']
);
const validateToken = (token) => {
return new Promise((resolve, reject) => {
if (!token) {
reject();
}
oauth.verifyIdToken(token, null, (err, ticket) => {
if (err) {
return reject(err);
}
const payload = ticket.getPayload();
const tokenIsOK = payload &&
payload.aud === authData.web.client_id &&
new Date(payload.exp * 1000) > new Date() &&
acceptableISSs.has(payload.iss) &&
payload.hd === 'your-special-domain.example';
return tokenIsOK ? resolve() : reject();
});
});
};
When defining your provider, pass in a hash at the end with the 'hd' parameter. You can read up on that here. https://developers.google.com/accounts/docs/OpenIDConnect#hd-param
E.g., for config/initializers/devise.rb
config.omniauth :google_oauth2, 'identifier', 'key', {hd: 'yourdomain.com'}
Here's what I did using passport in node.js. profile is the user attempting to log in.
//passed, stringified email login
var emailString = String(profile.emails[0].value);
//the domain you want to whitelist
var yourDomain = '#google.com';
//check the x amount of characters including and after # symbol of passed user login.
//This means '#google.com' must be the final set of characters in the attempted login
var domain = emailString.substr(emailString.length - yourDomain.length);
//I send the user back to the login screen if domain does not match
if (domain != yourDomain)
return done(err);
Then just create logic to look for multiple domains instead of just one. I believe this method is secure because 1. the '#' symbol is not a valid character in the first or second part of an email address. I could not trick the function by creating an email address like mike#fake#google.com 2. In a traditional login system I could, but this email address could never exist in Google. If it's not a valid Google account, you can't login.
Since 2015 there has been a function in the library to set this without needing to edit the source of the library as in the workaround by aaron-bruce
Before generating the url just call setHostedDomain against your Google Client
$client->setHostedDomain("HOSTED DOMAIN")
For login with Google using Laravel Socialite
https://laravel.com/docs/8.x/socialite#optional-parameters
use Laravel\Socialite\Facades\Socialite;
return Socialite::driver('google')
->with(['hd' => 'pontomais.com.br'])
->redirect();

Resources