I am trying to use graph API call to return events of a user who logged in to my app. However , $event is null all the time although I have created bunch of events myself, could anyone help?
Here is my code from login to the event api call:
require_once('AppInfo.php');
// Enforce https on production
if (substr(AppInfo::getUrl(), 0, 8) != 'https://' && $_SERVER['REMOTE_ADDR'] != '127.0.0.1') {
header('Location: https://'. $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
exit();
}
// This provides access to helper functions defined in 'utils.php'
require_once('utils.php');
require_once('sdk/src/facebook.php');
$facebook = new Facebook(array(
'appId' => AppInfo::appID(),
'secret' => AppInfo::appSecret(),
'sharedSession' => true,
'trustForwarded' => true,
));
$user_id = $facebook->getUser();
if ($user_id) {
try {
// Fetch the viewer's basic information
$basic = $facebook->api('/me');
} catch (FacebookApiException $e) {
// If the call fails we check if we still have a user. The user will be
// cleared if the error is because of an invalid accesstoken
if (!$facebook->getUser()) {
header('Location: '. AppInfo::getUrl($_SERVER['REQUEST_URI']));
exit();
}
}
// This fetches some things that you like . 'limit=*" only returns * values.
// To see the format of the data you are retrieving, use the "Graph API
// Explorer" which is at https://developers.facebook.com/tools/explorer/
$likes = idx($facebook->api('/me/likes?limit=4'), 'data', array());
// This fetches 4 of your friends.
$friends = idx($facebook->api('/me/friends?limit=4'), 'data', array());
// And this returns 16 of your photos.
$photos = idx($facebook->api('/me/photos?limit=16'), 'data', array());
// Fetch the event information
// $events = idx($facebook->api('/me/events?limit=5'), 'data', array());
$events = $facebook->api('/me/events','GET');
print("11111");
if($events == null) print("empty");
I see no login code in your example, are you sure the user is logged in? See PHP SDK "Usage": https://github.com/facebook/facebook-php-sdk
Anyway, i just tried this in one of my projects, i get the user events with the same call but i need the permission "user_events":
$facebook->getLoginUrl(array('scope' => 'user_events'));
Related
I want for my backend server (node.js) make a call through aws-sdk library to see if exists a user with specific mail. Is there a proper method to do this or a work arround without using user's credentials to do this procedure?
Yes this can be done the listUsers() AWS Javascript CognitoIdentityServiceProvider() API.
Call listUsers() to check if user exists
const cognito = new AWS.CognitoIdentityServiceProvider();
async function isEmailRegistered(email) {
//Check if user email is registered
var params = {
UserPoolId: 'eu-west-1_3bqeRjkSu', /* required */
AttributesToGet: [
'email',
],
Filter: "email = \"" + email + "\"",
};
return cognito.listUsers(params).promise();
}
Call and handle result
await isEmailRegistered(qsp.email).then( data => {
if (data.Users.length === 0) {
//User does not exist
} else {
//User does exist
}
}).catch(err => {
console.log("error: " + JSON.stringify(err))
});
I am attempting to set up passwordless sms authentication using amplify in an IOS project. I have lambda triggers taken from a github repo set up this way:
CreateAuthChallenge
// ### About this Flow ###
// Using Custom Auth Flow through Amazon Cognito User Pools with Lambda Triggers to complete a 'CUSTOM_CHALLENGE'.
//
// ### About this function ###
// This CreateAuthChallengeSMS function (2nd of 4 triggers) creates the type of 'CUSTOM_CHALLENGE' as a one-time pass code sent via SMS. A one-time randomly generated 6-digit code (passCode)
// is sent via SMS (through Amazon SNS) to the user's mobile phone number during authentication. The generated passCode is stored in privateChallengeParameters.passCode and passed to the VerifyAuthChallenge function
// that will verify the user's entered passCode (received via SMS) into the mobile/web app matches the passCode passed privately through privateChallengeParameters.passCode.
// ### Next steps ###
// Instead of using the "crypto-secure-random-digit" library to generate random 6-digit codes, create a base32 secret for the user (if not exist) and
// generate a 6-digit code based on this secret. Much like TOTP except for the secret is never shared with the user. With a base32 secret associated with the user,
// we can easily switch from 6-digit code via SMS to 6-digit code generated based on shared secret via TOTP using the OATH module of a YubiKey or an authenticator app.
//
// Updated: Jan 6, 2020
'use strict';
const crypto_secure_random_digit = require("crypto-secure-random-digit");
const AWS = require("aws-sdk");
var sns = new AWS.SNS();
// Main handler
exports.handler = async (event = {}, context, callback) => {
console.log('RECEIVED event: ', JSON.stringify(event, null, 2));
let passCode;
var phoneNumber = event.request.userAttributes.phone_number;
// The first CUSTOM_CHALLENGE request for authentication from
// iOS AWSMobileClient actually comes in as an "SRP_A" challenge (a bug in the AWS SDK for iOS?)
// web (Angular) comes in with an empty event.request.session
if (event.request.session && event.request.session.length && event.request.session.slice(-1)[0].challengeName == "SRP_A" || event.request.session.length == 0) {
passCode = crypto_secure_random_digit.randomDigits(6).join('');
await sendSMSviaSNS(phoneNumber, passCode);
} else {
const previousChallenge = event.request.session.slice(-1)[0];
passCode = previousChallenge.challengeMetadata.match(/CODE-(\d*)/)[1];
}
event.response.publicChallengeParameters = { phone: event.request.userAttributes.phone_number };
event.response.privateChallengeParameters = { passCode };
event.response.challengeMetadata = `CODE-${passCode}`;
console.log('RETURNED event: ', JSON.stringify(event, null, 2));
callback(null, event)
};
// Send secret code over SMS via Amazon Simple Notification Service (SNS)
async function sendSMSviaSNS(phoneNumber, passCode) {
const params = { "Message": "[Mapwork] Your secret code: " + passCode, "PhoneNumber": phoneNumber };
await sns.publish(params).promise();
}
DefineAuthChallenge
// ### About this Flow ###
// Using Custom Auth Flow through Amazon Cognito User Pools with Lambda Triggers to complete a 'CUSTOM_CHALLENGE'. This is the same flow as one-time passcode generated and sent via SMS or Email.
// Instead, the service and user share a secret that was created during registration and both generate a 6-digit code based on the shared secret.
// If the two codes (typically only good for 30 seconds) match, the user is authenticated.
//
// ### About this function ###
// This DefineAuthChallengeCustom function (1st and 4th of 4 triggers) defines the type of challenge-response required for authentication.
// For HOTP, TOTP, U2F, or WebAuthn flows, we'll always use 'CUSTOM_CHALLENGE' and this function code won't change between the various auth methods.
// ### Next steps ###
// Updated: June 12, 2020
'use strict';
exports.handler = async (event) => {
console.log('RECEIVED event: ', JSON.stringify(event, null, 2));
// The first auth request for CUSTOM_CHALLENGE from the AWSMobileClient (in iOS native app) actually comes in as an "SRP_A" challenge (BUG in AWS iOS SDK), so switch to CUSTOM_CHALLENGE and clear session.
if (event.request.session && event.request.session.length && event.request.session.slice(-1)[0].challengeName == "SRP_A") {
console.log('New CUSTOM_CHALLENGE', JSON.stringify(event, null, 2));
event.request.session = [];
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
}
// User successfully answered the challenge, succeed with auth and issue OpenID tokens
else if (event.request.session &&
event.request.session.length &&
event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE' &&
event.request.session.slice(-1)[0].challengeResult === true) {
console.log('The user provided the right answer to the challenge; succeed auth');
event.response.issueTokens = true;
event.response.failAuthentication = false;
}
// After 3 failed challenge responses from user, fail authentication
else if (event.request.session && event.request.session.length >= 4 && event.request.session.slice(-1)[0].challengeResult === false) {
console.log('FAILED Authentication: The user provided a wrong answer 3 times');
event.response.issueTokens = false;
event.response.failAuthentication = true;
}
// The user did not provide a correct answer yet; present CUSTOM_CHALLENGE again
else {
console.log('User response incorrect: Attempt [' + event.request.session.length + ']');
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
}
console.log('RETURNED event: ', JSON.stringify(event, null, 2));
return event;
};
VerifyAuthChallenge
// ### About this Flow ###
// Using Custom Auth Flow through Amazon Cognito User Pools with Lambda Triggers to complete a 'CUSTOM_CHALLENGE'. This custom challenge is using
// a randomly generated 6-digit sent to the user via SMS using Amazon SNS.
//
// ### About this function ###
// This VerifyAuthChallengeSMS function (3rd of 4 triggers) takes the user's 6-digit code sent via event.request.challengeAnswer parameter
// and returns TRUE if the user's passCode matches event.request.privateChallengeParameters.passCode.
// ### Next steps ###
'use strict';
exports.handler = async (event) => {
console.log('RECEIVED Event: ', JSON.stringify(event, null, 2));
let expectedAnswer = event.request.privateChallengeParameters.passCode || null;
if (event.request.challengeAnswer === expectedAnswer) {
event.response.answerCorrect = true;
}
else {
event.response.answerCorrect = false;
}
console.log('RETURNED Event: ', JSON.stringify(event, null, 2));
return event;
};
And then I invoke Auth.signIn() from the ios mobile device. However,I receive this error when I do so:
Sign in failed AuthError: Invalid lambda function output : Invalid JSON
I have been struggling for hours with this one and any help would be appreciated.
I have to validate some attributes to the files , convert them to png and move them to amazon S3 service , but i need to move the file to the bucket only if the validation in controller is successful, the customer requirement is to use a middle ware to achieve this. is there a way to do it even when is necessary to use the $request-> withAttribute() ?
Yes, indeed. Middleware is just another layer of your callable stack.
Whether it is applied before or after is defined within your code:
<?php
// Add middleware to your app
$app->add(function ($request, $response, $next) {
$response->getBody()->write('BEFORE');
$response = $next($request, $response);
$response->getBody()->write('AFTER');
return $response;
});
$app->get('/', function ($request, $response, $args) {
$response->getBody()->write(' Hello ');
return $response;
});
$app->run();
This would output this HTTP response body:
BEFORE Hello AFTER
So, in your case I'd go for something like this:
<?php
class AfterMiddleware
{
public function __invoke($request, $response, $next)
{
// FIRST process by controller
$response = $next($request, $response);
// You can still access request attributes
$attrVal = $request->getAttribute('foo');
// THEN check validation status and act accordingly
$isValid = true;
if ($isValid) {
// Send to the bucket
}
}
}
I have configured an ASOS OpenIdConnect Server using and an asp.net core mvc app that uses the "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0 and "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0". I have tested the "Authorization Code" workflow and everything works.
The client web app processes the authentication as expected and creates a cookie storing the id_token, access_token, and refresh_token.
How do I force Microsoft.AspNetCore.Authentication.OpenIdConnect to request a new access_token when it expires?
The asp.net core mvc app ignores the expired access_token.
I would like to have openidconnect see the expired access_token then make a call using the refresh token to get a new access_token. It should also update the cookie values. If the refresh token request fails I would expect openidconnect to "sign out" the cookie (remove it or something).
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = "myClient",
ClientSecret = "secret_secret_secret",
PostLogoutRedirectUri = "http://localhost:27933/",
RequireHttpsMetadata = false,
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true,
ResponseType = OpenIdConnectResponseType.Code,
AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet,
Authority = http://localhost:27933,
MetadataAddress = "http://localhost:27933/connect/config",
Scope = { "email", "roles", "offline_access" },
});
It seems there is no programming in the openidconnect authentication for asp.net core to manage the access_token on the server after received.
I found that I can intercept the cookie validation event and check if the access token has expired. If so, make a manual HTTP call to the token endpoint with the grant_type=refresh_token.
By calling context.ShouldRenew = true; this will cause the cookie to be updated and sent back to the client in the response.
I have provided the basis of what I have done and will work to update this answer once all work as been resolved.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookies",
ExpireTimeSpan = new TimeSpan(0, 0, 20),
SlidingExpiration = false,
CookieName = "WebAuth",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = context =>
{
if (context.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(context.Properties.Items[".Token.expires_at"]);
if (expire > DateTime.Now) //TODO:change to check expires in next 5 mintues.
{
logger.Warn($"Access token has expired, user: {context.HttpContext.User.Identity.Name}");
//TODO: send refresh token to ASOS. Update tokens in context.Properties.Items
//context.Properties.Items["Token.access_token"] = newToken;
context.ShouldRenew = true;
}
}
return Task.FromResult(0);
}
}
});
You must enable the generation of refresh_token by setting in startup.cs:
Setting values to AuthorizationEndpointPath = "/connect/authorize"; // needed for refreshtoken
Setting values to TokenEndpointPath = "/connect/token"; // standard token endpoint name
In your token provider, before validating the token request at the end of the HandleTokenrequest method, make sure you have set the offline scope:
// Call SetScopes with the list of scopes you want to grant
// (specify offline_access to issue a refresh token).
ticket.SetScopes(
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess);
If that is setup properly, you should receive a refresh_token back when you login with a password grant_type.
Then from your client you must issue the following request (I'm using Aurelia):
refreshToken() {
let baseUrl = yourbaseUrl;
let data = "client_id=" + this.appState.clientId
+ "&grant_type=refresh_token"
+ "&refresh_token=myRefreshToken";
return this.http.fetch(baseUrl + 'connect/token', {
method: 'post',
body : data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
});
}
and that's it, make sure that your auth provider in HandleRequestToken is not trying to manipulate the request that is of type refresh_token:
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsPasswordGrantType())
{
// Password type request processing only
// code that shall not touch any refresh_token request
}
else if(!context.Request.IsRefreshTokenGrantType())
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid grant type.");
return;
}
return;
}
The refresh_token shall just be able to pass through this method and is handled by another piece of middleware that handles refresh_token.
If you want more in depth knowledge about what the auth server is doing, you can have a look at the code of the OpenIdConnectServerHandler:
https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/master/src/AspNet.Security.OpenIdConnect.Server/OpenIdConnectServerHandler.Exchange.cs
On the client side you must also be able to handle the auto refresh of the token, here is an example of an http interceptor for Angular 1.X, where one handles 401 reponses, refresh the token, then retry the request:
'use strict';
app.factory('authInterceptorService',
['$q', '$injector', '$location', 'localStorageService',
function ($q, $injector, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var $http;
var _request = function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
};
var _responseError = function (rejection) {
var deferred = $q.defer();
if (rejection.status === 401) {
var authService = $injector.get('authService');
console.log("calling authService.refreshToken()");
authService.refreshToken().then(function (response) {
console.log("token refreshed, retrying to connect");
_retryHttpRequest(rejection.config, deferred);
}, function () {
console.log("that didn't work, logging out.");
authService.logOut();
$location.path('/login');
deferred.reject(rejection);
});
} else {
deferred.reject(rejection);
}
return deferred.promise;
};
var _retryHttpRequest = function (config, deferred) {
console.log('autorefresh');
$http = $http || $injector.get('$http');
$http(config).then(function (response) {
deferred.resolve(response);
},
function (response) {
deferred.reject(response);
});
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
authInterceptorServiceFactory.retryHttpRequest = _retryHttpRequest;
return authInterceptorServiceFactory;
}]);
And here is an example I just did for Aurelia, this time I wrapped my http client into an http handler that checks if the token is expired or not. If it is expired it will first refresh the token, then perform the request. It uses a promise to keep the interface with the client-side data services consistent. This handler exposes the same interface as the aurelia-fetch client.
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import {AuthService} from './authService';
#inject(HttpClient, AuthService)
export class HttpHandler {
constructor(httpClient, authService) {
this.http = httpClient;
this.authService = authService;
}
fetch(url, options){
let _this = this;
if(this.authService.tokenExpired()){
console.log("token expired");
return new Promise(
function(resolve, reject) {
console.log("refreshing");
_this.authService.refreshToken()
.then(
function (response) {
console.log("token refreshed");
_this.http.fetch(url, options).then(
function (success) {
console.log("call success", url);
resolve(success);
},
function (error) {
console.log("call failed", url);
reject(error);
});
}, function (error) {
console.log("token refresh failed");
reject(error);
});
}
);
}
else {
// token is not expired, we return the promise from the fetch client
return this.http.fetch(url, options);
}
}
}
For jquery you can look a jquery oAuth:
https://github.com/esbenp/jquery-oauth
Hope this helps.
Following on from #longday's answer, I have had success in using this code to force a client refresh without having to manually query an open id endpoint:
OnValidatePrincipal = context =>
{
if (context.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(context.Properties.Items[".Token.expires_at"]);
if (expire > DateTime.Now) //TODO:change to check expires in next 5 mintues.
{
context.ShouldRenew = true;
context.RejectPrincipal();
}
}
return Task.FromResult(0);
}
I have a list of user id's ( may or may not be my friends) I want to get ALL the public possible information about them.. However, I am only getting back Name, Id and Photo. Where am I going wrong?
FB.login(function(){
/* make the API call */
FB.api(
"/{event-id}/attending",
function (response) {
if (response && !response.error) {
var array = response.data;
array.forEach(function(eachUser){
//console.log(eachUser);
FB.api(
"/"+ eachUser.id,
function (response) {
if (response && !response.error) {
console.log(response);
}
}
);
});
}
}
);
}, {scope: 'user_events, user_education_history , user_about_me , user_work_history , user_location , user_website'}); //,age_range, bio , context , education
Even if it´s set to public, all the other data is only available if the user authorizes your App with the appropriate permission. So in the list of attending users, name/id/photo is the only data you can get.