I'm trying to implement the Microsoft Graph for OneDrive access in PHP. I'm going by this documentation.
I've registered my app at https://apps.dev.microsoft.com/ and got an App ID and a secret back.
Now, for the token flow, I'm emitting the following link:
$LogonLink =
"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?".
http_build_query(array(
'client_id' => $MSGraphAppID,
'response_type' => 'code',
'redirect_uri' => 'http://www.example.com/ongraph.php',
'response_mode' => 'query',
'scope' => 'offline_access Files.ReadWrite'));
The logon and permissions UI works as expected, my app's name and icon comes up. So the App ID is recognized. Now, ongraph.php gets control with a code, and I emit the following POST:
$cu = curl_init('https://login.microsoftonline.com/common/oauth2/v2.0/token');
$Form = array(
'client_id' => $MSGraphAppID,
'scope' => 'offline_access Files.ReadWrite',
'code' => $_GET['Code'],
'redirect_uri' => 'http://www.example.com/ongraph.php',
'grant_type' => 'authorization_code',
'client_secret' => $MSGraphSecret
);
curl_setopt_array($cu, array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($Form)));
$r = curl_exec($cu);
I'm getting back error 400 and the following response:
{
"error": "unauthorized_client",
"error_description": "AADSTS70001: Application with identifier '0000000048205436' was not found in the directory 9188040d-6c67-4c5b-b112-36a304b66dad\r\nTrace ID: b0f7cba6-42b3-4f37-8438-5e8566ba1300\r\nCorrelation ID: ca51b601-86cb-4801-a330-354ffd3c715f\r\nTimestamp: 2017-11-08 17:12:33Z",
"error_codes": [70001],
"timestamp": "2017-11-08 17:12:33Z",
"trace_id": "b0f7cba6-42b3-4f37-8438-5e8566ba1300",
"correlation_id": "ca51b601-86cb-4801-a330-354ffd3c715f"
}
What am I missing here, please? I've tried creating a brand new app registration, same result.
EDIT: that's what the app registration site looks like:
The problem here is that you've registered your app as a Live SDK application:
For the v2 Endpoint (and Microsoft Graph), you need to register this as a Converged application:
The https://apps.dev.microsoft.com site handles both application types.
Related
I am building a web app where, basically, one user can pay to meet another user in a Google Meet. This would then create a Google Meet Calendar Event where the two users + one user from the company would be invited to a video meeting.
Basically, domain is example.com. This email address (service account), must invite attendee1#companyX.com and attendee2#companyY.com. When I put the event with no attendee, no problemo. As soon as I put the other attendees, I get:
{ "error": { "errors": [ { "domain": "calendar", "reason": "forbiddenForServiceAccounts", "message": "Service accounts cannot invite attendees without Domain-Wide Delegation of Authority." } ], "code": 403, "message": "Service accounts cannot invite attendees without Domain-Wide Delegation of Authority." } }
I want to use a service account since it's the company (example.com), it never changes and I don't want the website to break if the password/whatever changes.
Here is my code:
$client = new Google\Client();
$client->useApplicationDefaultCredentials();
$client->addScope(Google_Service_Calendar::CALENDAR);
$service = new Google_Service_Calendar($client);
$event = new Google_Service_Calendar_Event(array(
'summary' => 'Entretien vidéo',
'start' => array(
'dateTime' => '2015-05-28T09:00:00-07:00',
'timeZone' => 'America/Los_Angeles',
),
'end' => array(
'dateTime' => '2015-05-28T17:00:00-07:00',
'timeZone' => 'America/Los_Angeles',
),
'recurrence' => array(
'RRULE:FREQ=DAILY;COUNT=2'
),
'attendees' => [
[
'email' => "attendee#email.com"
]
],
"conferenceData"=> [
"createRequest" => [
"conferenceSolutionKey"=> [
"type"=> "hangoutsMeet"
],
"requestId"=> "7qxalsvy0exxaje"
]
],
));
$calendarId = 'primary';
$event = $service->events->insert($calendarId, $event);
printf('Event created: %s\n', $event->htmlLink); });
Can anybody help me about this?
Thanks!
To use the Google Calendar with your domain you need sign up for Gsuite
see https://support.google.com/a/answer/39410?hl=en
Prices plans: https://support.google.com/a/answer/1247360?hl=en
I am using Laravel 5.8.10, React 16.8, Laravel Echo Server 1.5.2, Redis 3.2.9, and Socket.io 2.2.0.
I am NOT using Pusher and don't want to use Pusher.
I am trying to create a basic chat system for site users. They log in normally using session authentication with email and password - all of that works fine.
There are 2 types of users: Brands and Influencers. Each has its own custom guard (web-brands & web-influencers). All session guards work normally.
I'm building the chat page using React. I can successfully join a public channel and receive messages on that public channel. However, the problem is when I try to make the channel private.
When I try to join a private channel, Laravel Echo Server sends an authentication request to: http://localhost:8000/broadcasting/auth.
But that returns the following 401 error:
{"message":"Unauthenticated."}
Client can not be authenticated, got HTTP status 401
Right now, I am trying to authenticate requests to /broadcasting/auth using a simple 'api_token' that is stored in the users tables (brands and influencers are the 2 users tables). This is a unique 60-character string.
I am trying this 'api_token' strategy because it sounds easier than setting up Laravel Passport, but perhaps I am wrong about that.
This is the constructor method from my React page:
import React, { Component } from 'react';
import Echo from "laravel-echo";
import Socketio from "socket.io-client";
constructor(props) {
super(props);
this.state = {
currentConversationId: conversations[0].id,
data: '',
};
this.selectConversation = this.selectConversation.bind(this);
let echo = new Echo({
broadcaster: 'socket.io',
host: 'http://localhost:6001',
client: Socketio,
auth: {
headers: {
// I currently have CSRF requirements disabled for /broadcasting/auth,
// but this should work fine once it is enabled anyway
'X-CSRF-Token': document.head.querySelector('meta[name="csrf-token"]'),
// I have the api_token hard-coded as I am trying to get it to work,
// but I have also used the javascript variable 'token' below
'api_token':'uUOyxRgCkVLKvp7ICZ0gXaELBPPbWEL0tUqz2Dv4TsFFc7JO4gv5kUi3WL3Q',
'Authorization':'Bearer: ' +'uUOyxRgCkVLKvp7ICZ0gXaELBPPbWEL0tUqz2Dv4TsFFc7JO4gv5kUi3WL3Q',
//'api_token':token,
//'Authorization':'Bearer: ' + token,
}
}
});
// Note that the ID of 1 is hardcoded for now until I get it to work
echo.private('brand.1')
.listen('SimpleMessageEvent', event => {
console.log('got something...');
console.log(event);
this.state.data = event;
});
}
Here you can see the in $php artisan route:list, the route is using auth:api middleware:
| GET|POST|HEAD | broadcasting/auth | Illuminate\Broadcasting\BroadcastController#authenticate | auth:api
Here is my BroadcastServiceProvider.php:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Broadcast::routes(['middleware' => ['auth:api']]);
require base_path('routes/channels.php');
}
}
Here is my auth.php:
<?php
return [
'defaults' => [
'guard' => 'web-brands',
'passwords' => 'brands',
],
'guards' => [
'web-brands' => [
'driver' => 'session',
'provider' => 'brands',
],
'web-influencers' => [
'driver' => 'session',
'provider' => 'influencers',
],
'api' => [
'driver' => 'token',
'provider' => 'brands2',
],
],
'providers' => [
'brands' => [
'driver' => 'eloquent',
'model' => App\Brand::class,
],
'influencers' => [
'driver' => 'eloquent',
'model' => App\Influencer::class,
],
'brands2' => [
'driver' => 'database',
'table' => 'brands',
],
],
'passwords' => [
'brands' => [
'provider' => 'brands',
'table' => 'password_resets',
'expire' => 60,
],
'influencers' => [
'provider' => 'influencers',
'table' => 'password_resets',
'expire' => 60,
],
],
];
Here is my channels.php:
Broadcast::channel('brand.{id}',true);
Note that I have the brand.{id} set it to return true by default. I have also tried this for channels.php:
Broadcast::channel('brand.{id}', function ($brand,$id) {
return $brand->id === Brand::find($id)->id;
});
I have already tried testing the simple api_token method by using a dummy route:
Route::get('test-test-test',function(){return 'asdf';})->middleware('auth:api');
This test works:
http://localhost:8000/test-test-test results in redirect
http://localhost:8000/test-test-test?api_token=123 results in redirect
http://localhost:8000/test-test-test?api_token=[the actual correct 60-character token] results in 'asdf'
Here is some info from my .env:
BROADCAST_DRIVER=redis
QUEUE_DRIVER=redis
CACHE_DRIVER=file
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
Here is my laravel-echo-server.json:
{
"authHost": "http://localhost:8000",
"authEndpoint": "/broadcasting/auth",
"clients": [],
"database": "redis",
"databaseConfig": {
"redis": {},
"sqlite": {
"databasePath": "/database/laravel-echo-server.sqlite"
}
},
"devMode": true,
"host": null,
"port": "6001",
"protocol": "http",
"socketio": {},
"sslCertPath": "",
"sslKeyPath": "",
"sslCertChainPath": "",
"sslPassphrase": "",
"subscribers": {
"http": true,
"redis": true
},
"apiOriginAllow": {
"allowCors": false,
"allowOrigin": "",
"allowMethods": "",
"allowHeaders": ""
}
}
Perhaps I am not sending the api_token correctly in the header of the laravel echo request?
UPDATE/EDIT:
Now I have tried removing the auth:api middleware for the /broadcasting/auth route. I'm not sure if that was the correct thing to do.
That now produces a 403 error:
Client can not be authenticated, got HTTP status 403
UPDATE 2 - IMPORTANT
So I know this is not recommended, but I started changing some things inside of the laravel source files... I got it to work finally and now that I have figured it out, I would like to override the source files that I changed instead of actually changing them. I did save the originals so I can easily revert back.
One big challenge was that while changing the source files, I was not able to use the where() method, only the find() method to lookup users.
The key function that needed changing was retrieveUser() (which is located inside of Illuminate/Broadcasting/Broadcasters/Broadcaster.php.
The problem was that it kept trying to run:
return $request->user();
...but that user() function never worked, which is why it always returned a 403 forbidden error. I think it is because the actual Laravel Echo request was sent from React (in javascript frontend), so there was no user object attached to the request. In other words, it was like a guest making the request. That explains why the public channels worked, but the private ones didn't.
I never did figure out how to get the user information to be sent with the request through React, but I did figure out a workaround.
Basically what I had to do:
In my controller, encrypt the ID of the user and pass it to javascript as a variable.
Pass the encrypted ID variable through the Echo request as part of the header.
Modify the retrieveUser() function to use find(Crypt::decrypt($id)) to lookup the user instead of ->user() (or where() which was strangely not allowed).
From what I can tell, this seems like a decent strategy from a security perspective, but perhaps readers could point out if that is actually correct.
To hack your way into a private channel, you would have to know the ID of the channel you want to listen to, then pass it as an encrypted variable in the header of the request.
Maybe a potential hacker could say that he/she wants to listen to private channel 'brand.1' and all they would have to do is encrypt the number 1 and pass it through the header. I guess I don't know how that works enough to know whether that is possible.
Anyway my goals now are:
converting this into an override setup instead of explicitly changing the Laravel source code.
figuring out if passing the encrypted ID through the request header is secure enough for production.
It does seem like the encrypted ID in the header (which does change every time you run the request) is more secure than simply passing through an 'api_token' which would be a value stored in the users table and is what most people seem to do.
I created an web app which it uses laravel default registration(auth), I've tested passport oauth2 client access token from taylor tutorial. My web app uses angular js for UI and laravel for backend , so I need to create user, when create user request is sent from angular and then create a global access token to give it in my response to angular which then in all later request I use it to authenticate requests.
actually I want to implement oauth2 authentication for my web app, but so far I've searched a lot but I couldn't find any useful step by step tutorial for it.
anyone can help me out?
FYI: I'm using laravel 5.3 with passport enabled and angular js 1.5 for frontend.
Use JWT token based authentication here you can learn about jwt https://jwt.io/
I've solved this.
I've Customized laravel auth for login and register and created a method which will send a request to the server to create an access token for registering user or log in.
I've set up passport and test it as taylor did in his toturial.
then in AuthenticatesUsers.php I've changed sendloginResponse method response like :
protected function sendLoginResponse(Request $request)
{
isset($request->token) ? $token = $request->token : $token = null;//Check if login request contain token
$request->session()->regenerate();
$this->clearLoginAttempts($request);
return $this->authenticated($request, $this->guard()->user())
? $this->StatusCode($token,$this->credentials($request),$status=false) : $this->StatusCode($token,$this->credentials($request),$status=true);
}
And I have added this method to request access token and send it as json response :
public function StatusCode($token,$user,$status){
if( $token != null && $token != ''){
return ($status == true) ? response()->json(GetToken($user),200) : response()->json(['Message' => 'Failed to log in'],403);
}
function GetToken($userobject)
{
$http = new Client;
$response = $http->post('http://localhost/iranAd/public/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => '1',
'client_secret' => 'xKqNbzcXyjySg20nVuVLw5nk5PAMhFQOQwRTeTjd',
'username' => $userobject['email'],
'password' => $userobject['password'],
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
}
function RefreshToken($token,$userobject)
{
$http = new Client;
$response = $http->post('http://localhost/iranAd/public/oauth/token', [
'form_params' => [
'grant_type' => 'refresh_token',
'refresh_token' => 'refresh_token',
'client_id' => '1',
'client_secret' => 'xKqNbzcXyjySg20nVuVLw5nk5PAMhFQOQwRTeTjd',
'username' => $userobject['email'],
'password' => $userobject['password'],
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
}
return ($status == true) ? response()->json(GetToken($user),200) : response()->json(['Message' => 'Failed to log in'],403);
}
Same Procedure for register users.
The purpose of this post is not to answer(as already answered) but to give more info to other readers who eventually need more info on topic.
This is very helpfull tutorial just on this issue Implementing Vue.js 2.0 and Laravel 5.3 with CORS problem solution
Check this one and 2 next clips.
Some of this you can find in shorter form here here
I am using basic authentication in my project to access Api. In ApiController, I added below code in beforeFilter:
$this->Auth->config('authenticate', [
'Basic' => [
'fields' => ['username' => 'username', 'password' => 'api_key'],
'userModel' => 'Users'
]
]);
So from chrome postman application, I am sending post request with basic auth credentials. for example like below:
So when I send a request, I get unauthorized error back.
you are sending a post request with a 'password' field
Your application is expecting a 'api_key' field that would contain the password.
I think you missed this one script in your model entity.
use Cake\Auth\DefaultPasswordHasher;
protected function _setPassword($password)
{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher)->hash($password);
}
}
Put this one in Model/Entity/User.php
I am working on a simple Angular application and I wish to have my back end data from an API I created myself in Laravel. The API is sending back data just fine. But since I am developing my Angular application separately, I have an error when I use a service in Angular to fetch my resource.
Error
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8001/airports. This can be fixed by moving the resource to the same domain or enabling CORS.
So now I have installed a package in Laravel which is supposed to solve the issue but I am not sure how to use it. The cofig file of the package looks like:
'defaults' => array(
'supportsCredentials' => false,
'allowedOrigins' => array(),
'allowedHeaders' => array(),
'allowedMethods' => array(),
'exposedHeaders' => array(),
'maxAge' => 0,
'hosts' => array(),
),
'paths' => array(
'api/*' => array(
'allowedOrigins' => array('*'),
'allowedHeaders' => array('*'),
'allowedMethods' => array('*'),
'maxAge' => 3600,
),
'*' => array(
'allowedOrigins' => array('*'),
'allowedHeaders' => array('Content-Type'),
'allowedMethods' => array('POST', 'PUT', 'GET', 'DELETE'),
'maxAge' => 3600,
'hosts' => array('api.*'),
),
),
I am still getting the same error. Now what is it that I have to different in order to allow CORS?
Note: I have WAMP installed. I am developing both my Angular application and Laravel API on the same local server.
I ran into something similar with EmberJS instead of Angular. Laravel setup looks correct but I think you need to enable CORS support in Angular.
From a Chrome developer tutorial:
A word on Content Security Policy
Unlike many other JS MVC frameworks,
Angular v1.1.0+ requires no tweaks to work within a strict CSP. It
just works, out of the box!
However, if you're using an older version of Angular between v1.0.1
and v1.1.0, you'll need tell Angular to run in a "content security
mode". This is done by including the ngCsp directive alongside ngApp:
<html data-ng-app data-ng-csp>
You can find the whole article here.