How to get User Access Token claims for Facebook registered app - reactjs

I am used to working with Azure Active Directory. I know that if I register an app, I can use MSALs library in React (which uses OIDC in the background) to get a JWT Token (I actually get 2, an ID and an Access Token, but that is beyond the scope of this question). I can then make a call from my front end to my back end API, and send that token through as authentication. In the backend, I check the signature, and also check that the app of the token is the app that I registered, and also that the token is generated for use 'X'. So with that single token I can protect my backend API and make sure that the frontend call is legitimately coming from the SPA (unless the token gets stolen), and that the user 'X' is the one whose session is active for the given API call.
Now, for a personal project, I am trying to work with Facebook as an identity provider. I registered my app in facebook, and in my freshly created react project I have:
<React.StrictMode>
<FacebookLogin
appId="<App ID Here>"
autoLoad={false}
fields="name,email,picture"
callback={responseFacebook}
/>
<App />
</React.StrictMode>
and
const responseFacebook = (response) => {
console.log(response);
}
Now, when I click the button, I see the following in the console:
{
"name": "<My Name Here>",
"email": "<My Mail Here>",
"picture": "<Picture Data Here>",
"id": "<ID Here>",
"accessToken": "EAAJlEXBfRWgBAGHTP0ZAgRttMSMZzAxWM4B6aAZBL7FlGSWgZBITj9HXb2mZAVuoyIc1V43StjYwuwcsSTZBEd4aPK6iGwbNGRvN7o5PH3ZAOymGxtb5W8k2BYYdAW5w1frXt8JeEvJI3SCoOsJqMVHm9mo5N7NpSZBKs74wqu8LvgQbqqYfvialeSP5LwtUMMS51pMsN5Kwv7aWPud0",
"userID": "<User ID Here>",
"expiresIn": 6488,
"signedRequest": "<A Bunch of Gibberish>",
"graphDomain": "facebook",
"data_access_expiration_time": 1677528712
}
Now, about the access token, that is no JWT and I dont know how to use it for authenticating against my OWN backend API. I know I can use that token to make requests to facebook GraphAPI and get more info about the user (in this case, me). But, when I make a request to my backend, and I send that token, I have no clue how to verify its a legitimate token, meaning:
Verify its signed by facebook
Verify the APP ID is the one im looking for
Verify for which user was the token generated
In good old JWT, I can simply check the claims, but I have no idea what this is and how to prove to a backend server, or any third party for that matter, that the token was generated by 'X' user interacting with 'Y' app registered on facebook

Well, as per #CBroe's suggestion, I used the debug token Graph endpoint to retrieve info about the token (Doc https://developers.facebook.com/docs/graph-api/reference/v15.0/debug_token).
So in the front end, I get the info shown in the question, and I need to pass that info to a backend API, as well as proof of said info legitimacy. So, in pseudocode:
// Frontend
body = {
"name": "<My Name Here>",
"email": "<My Mail Here>",
"picture": "<Picture Data Here>",
"id": "<ID Here>",
"accessToken": "EAAJlEXBfRWgBAGHTP0ZAgRttMSMZz...",
"userID": "<User ID Here>",
"expiresIn": 6488,
"signedRequest": "<A Bunch of Gibberish>",
"graphDomain": "facebook",
"data_access_expiration_time": 1677528712
};
RestClient.call("myapiurl/myendpoint", "HTTP_METHOD", body=body);
and
// Backend
function checkValidity(info) {
version = "v15.0"
token = info.accessToken;
endpointURL = "https://graph.facebook.com/${version}/debug_token?input_token=${token}";
headers = { "Authorization": "Bearer ${token}" };
response = RestClient.call(endpointURL, "GET", headers=headers);
// Note that the token is sent twice, once for Auth purposes, and another time as payload for the debug endpoint
/*
The response will look like:
{
"data": {
"app_id": "<APP ID>",
"type": "USER",
"application": "<APP Name>",
"data_access_expires_at": 1677601862,
"expires_at": 1669831200,
"is_valid": true,
"scopes": [
"email",
"public_profile"
],
"user_id": "<USER ID>"
}
}
*/
if (response.data.app_id == "<MY APP ID>" && response.data.user_id == info.userID) {
return true;
} else {
return false;
}
}
app.post('/myendpoint', function(req, res) {
checkValidity(req.body);
res.send('some result');
});
This way we can be sure that the token is valid (as if it wasn't, the facebook endpoint would have returned an error. Also, we can confirm the token was generated by userID for appID. In your backend data system you need to use the userID as your main reference, because its the only guaranteed value to have to map that user's activity in that appID (unless the user allows the app to read the email).
Note that the userID is different for each user-app pair, so if you want data such as the user email, you need to use the token to hit facebook's /user endpoint with that userID to get the info you need.
You should not trust the email field sent in the body because, since the AppID is public as its dealt with in the frontend, someone could make up a mock frontend and login with their own account (therefore generating a valid token with its matching userID) and forge the rest of the fields in the body.
This assumes the token has not been stolen (as with any bearer token solution), and that the user is careful and aware of where he/she is signing into, (as a malicious phishing clone frontend could trick the user into SSOing into a fake site).

Related

How to access user objects at the front end using Framework's TokenAuthentication

How to access user objects at the front end using Django Framework's TokenAuthentication? I was able to successfully get a token, which includes the user id and username of the user logged in but I am unsure of how to get the rest of the user information in the front end.
I am a beginner in programing, so your help is much appreciated.
There are too many ways to do that - or simply you can retrieve user info from user model by using user_id and return it as a json in response context.
from rest_framework import response, status
def user_info(request)
user_ins = User.objects.get(id=request.user.id)
context = {
"user_info": {
"first_name": user_ins.first_name,
"last_name": user_ins.last_name,
"username": user_ins.username,
"email": user_ins.email
}
return response.Response(status=status.HTTP_200_OK, data=context)

How to receive user photos in MS Teams Bot which installed in different organizations?

According to the documentation https://learn.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-beta - in order to get the user photo we need to perform HTTP request https://graph.microsoft.com/beta/users/{userId}/photo/$value providing the access token and user id.
Using the client credentials flow I can receive the access token for a specifiŅ organization (tenant id), for example:
public static String getAppAccessToken(String[] scopes) {
ConfidentialClientApplication cca;
try {
cca = ConfidentialClientApplication.builder(applicationId, ClientCredentialFactory.createFromSecret(applicationSecret))
.authority("https://login.microsoftonline.com/<<tenantId>>/")
.build();
} catch (MalformedURLException e) {
return null;
}
Set<String> scopeSet = Set.of(scopes);
ClientCredentialParameters clientCredentialParam = ClientCredentialParameters.builder(
scopeSet)
.build();
CompletableFuture<IAuthenticationResult> future = cca.acquireToken(clientCredentialParam);
return future.join().accessToken();
}
But the access token received in such way allows to receive photos only for users of the organization, which has the same tenant id with the tenant id of the bot app registered in Azure Active Directory App Registration.
Is it possible to receive the photos of the users from other organizations where my bot is installed, using client credentials flow token?
In this case I receive:
{
"error": {
"code": "UnknownError",
"message": "{\"error\":{\"code\":\"NoPermissionsInAccessToken\",\"message\":\"The token contains no permissions, or permissions can not be understood.\",\"innerError\":{\"oAuthEventOperationId\":\"d90f2331-a22a-44d5-889e-c0c14ea9129e\",\"oAuthEventcV\":\"n+NecjM040KWn+2G+e5oFQ.1\",\"errorUrl\":\"https://aka.ms/autherrors#error-InvalidGrant\",\"requestId\":\"0b6bb579-94a7-47ac-8ff9-26ee893e5cb0\",\"date\":\"2021-01-28T00:57:52\"}}}",
"innerError": {
"date": "2021-01-28T00:57:52",
"request-id": "0b6bb579-94a7-47ac-8ff9-26ee893e5cb0",
"client-request-id": "0b6bb579-94a7-47ac-8ff9-26ee893e5cb0"
}
}
}
I'm certainly no expert on auth, but here are two suggestions to look in to, in case this can help:
"https://login.microsoftonline.com/<>/" -> use "organizations" instead of tenant id
check what scopes you're using (you don't list what you're trying, so it might be this)
Just try using "https://login.microsoftonline.com/common" endpoint and see if you can get this working. Please read more on that in detail here.
You can even send REST request to individual tenant's get the access token and access the information accordingly. Please be aware of having scopes while sending request.

How to create a case in a Salesforce Account using REST API and python Script

I need some help. I need to create a case in a Account with all the details basically, all the fields, using REST API but it I am not able to figure out, how to insert a record for creating a case.
Could you please guide me, how to create a case using REST API in Salesforce?
Do you use a library such as https://pypi.org/project/simple-salesforce/0.3/ or do you need to craft the REST messages manually?
You'd need to do it in 2 calls, login first (unless you have session id already) and then
POST to
https://yourinstance.my.salesforce.com/services/data/v48.0/sobjects/Case
with header
Authorization Bearer <session id goes here, sometimes called "access token" too>
and body
{
"Subject": "Hello world",
"Description": "Lorem ipsum dolor sit amet...",
"Origin":"Web",
"AccountId" :"0010g00001mbqU4"
}
should work like a charm (pass an account id value right for your org and you might have more fields to fill in).
So now "only" how to log in. That's a bigger topic and depends if it's a backend thing or you'll have a human interacting with it, maybe logging in to SF and then coming back to your org. There's bit of reading on that (simpler if you'd use SOAP API to log in)
For example this would work if I didn't redact all sensitive stuff
POST to
https://login.salesforce.com/services/oauth2/token
with header
Content-Type application/x-www-form-urlencoded
and body
grant_type=password
&client_id=(ask your admin for "connected app")
&client_secret=(ask your admin for "connected app")
&username=redacted%40example.com
&password=redacted
Should return
{
"access_token": "<session id here, use it as Authorization header in next calls>",
"instance_url": "<use this as base url of all next calls>",
"id": "<call GET to this (with the Auth header) to learn more about user",
"token_type": "Bearer",
"issued_at": "1593684589157",
"signature": "redacted"
}
Again - don't do it all by hand if you can, use one of Python libraries for Salesforce.
from simple_salesforce import Salesforce
sf = Salesforce(
username='user name of salesforce account',
password='password',
security_token='token')
Sample data
data ={
"Name" : "ABCD",
"Contact" : "123456789",
"Description":"Radio has damaged because of handling."
}
create record
x = sf.Account.create(data)

How Do I Authenticate a Service Account via Apps Script in order to Call a GCP Cloud Function

I'm trying hell hard to run a GCP Cloud Function from Apps Script. The Cloud function requires Authentication. However I keep getting thrown a "401 Error"
On My Google Project;
I've Created a Cloud function that requires Authentication
I've Created a Service Account that has Invoke (and edit) access to that function
I've Downloaded the JSON key for that service account and saved it as an object named CREDS in my Apps Script
This is my script so far:
const CREDS = {....JSON Key I downloaded from Cloud Console}
function base64Encode(str){
let encoded = Utilities.base64EncodeWebSafe(str)
return encoded.replace(/=+$/,'')
}
function encodeJWT(){
const privateKey = `Copied the PK from the CREDs file and replaced all the escaped whitespace as a string literal`;
let header = JSON.stringify({
alg: "RS256",
typ: "JWT",
});
let encodedHeader = base64Encode(header);
const now = Math.floor(Date.now() / 1000);
let payload = JSON.stringify({
"iss": "https://accounts.google.com",
"azp": "OAUTH CLIENT ID I CREATED ON GCP",
"aud": "OAUTH CLIENT ID I CREATED ON GCP",
"sub": CREDS.client_id,
"email": CREDS.client_email,
"email_verified": true,
// "at_hash": "TMTv8_OtKA539BBRxLoTBw", //Saw this in a reverse engineered Token but didnt know what to put
"iat": now.toString(),
"exp": (now + 3600).toString()
})
let encodedPayload = base64Encode(payload);
let toSign = [encodedHeader, encodedPayload].join('.')
let signature = Utilities.computeRsaSha256Signature(toSign, privateKey)
let encodedSignature = base64Encode(signature);
let jwt = [toSign, encodedSignature].join('.')
return jwt;
}
function testFireStore() {
let funcUrl = "https://[MY PROJECT].cloudfunctions.net/MyFunc"
const token = encodeJWT()
let options = {
headers:{
"Authorization": "Bearer " + token
}
}
let func = UrlFetchApp.fetch(funcUrl,options)
Logger.log(func.getContentText())
}
The actual Cloud func just gives a "Hello World" for now and it tests fine in the console
FYI, some steps I've done already
I've generated a token using gcloud on my local machine and used it in my apps script, that works fine
I've taken the said token and reverse engineered it on https://jwt.io
I've used the code here to create my JWT function which I checked back with https://jwt.io to ensure its in the correct format.
This Solution posted by #TheMaster in the comments to my solution solved the issue.
On the GCP side, I went in and enabled compute Engine and App Engine, then used this solution and it worked.
The only odd thing is that the target_audience requested there, I had to do a bit of reverse engineering to get it. I had to get the Identity Token from the command line tool, then use jwt.io to decode it, getting the AUD key...
but that aside, everythign worked like a charm

Why is Identity Server4 Logout not working? (without MS Identity)

I am trying to implement my own OAuth Server with IdentityServer4, and so far everything works except the logout.
I am not using Microsoft Identity, as I already have an existing WebApp with a WebApi which is handling the user-related CRUD operations. Thus I am using an existing Database for fetching Users and validating their username and PW. If validation is successful, my validation Method returns an object of type "AuthenticatedUser" (which is a UtilityClass I made).
Edit My Client is a Xamarin App, and using IdentityModel.OidcClient2 for login. I am testing with the UWP platform, Edit which uses WebAuthenticationBroker for Login/Logout calls.
Code I use is the one from the QuickStart UI Example, with a small modification to validate the users from my existing DB:
Edit Now I am explicitly creating Claims, ClaimsIdentity, and added CookieAuthenticationDefaults.AuthenticationScheme wherever possible.
//my method for user validation
AuthenticatedUser user = await _userService.ValidateCredentials(model.Username, model.Password);
//rest of login code from quickstart ui
if (user != null)
{
await _events.RaiseAsync(new UserLoginSuccessEvent(user.FirstName, user.Id.ToString(), user.FirstName));
// only set explicit expiration here if user chooses "remember me".
// otherwise we rely upon expiration configured in cookie middleware.
AuthenticationProperties props = null;
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
//things we know about the user that we wish to store on the cookie
var claims = new List<Claim>
{
new Claim(JwtClaimTypes.Role, user.RoleId.ToString()),
new Claim(JwtClaimTypes.Name, user.FirstName + " " + user.LastName),
new Claim(JwtClaimTypes.Subject, user.Id.ToString())
};
var userIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
//set the cookie using the SignInAsync method
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, props);
// issue authentication cookie with subject ID and username
await HttpContext.SignInAsync(user.Id.ToString(), user.FirstName, props);
//....
So far, this seems to work well. When the Login fails, I cannot access my protected Api, if the login succeeds, I get an AccessToken with the claims I requested, and I can access the protected Api methods as expected.
When I call the logout endpoint (done by a HTTP request to the endpoint, providing id_token_hint as query parameter), though, for some reason the User is not Authenticated - therefore my User is never Logged out by calling HttpContext.SignOutAsync().
if (User?.Identity.IsAuthenticated == true) //always evaluates to false?! why?
{
// delete local authentication cookie
await HttpContext.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
Edit After using WebAuthenticationBroker instead of a simple HTTP Request for calling the logout endpoint, the console Logs though state that "XamarinApp" got logged out. Even though HttpContext.SignOutAsync() was never called What does this mean? I doubt that this is Ok, but the app behaves as I want afterwards, e.g I can log in with a new user.
[16:43:12 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /connect/endsession matched to endpoint type Endsession
[16:43:12 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Endsession, successfully created handler: IdentityServer4.Endpoints.EndSessionEndpoint
[16:43:12 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.EndSessionEndpoint for /connect/endsession
[16:43:12 Debug] IdentityServer4.Endpoints.EndSessionEndpoint
Processing signout request for anonymous
[16:43:12 Debug] IdentityServer4.Validation.EndSessionRequestValidator
Start end session request validation
[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Start identity token validation
[16:43:12 Debug] IdentityServer4.EntityFramework.Stores.ClientStore
xamarinApp found in database: True
[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Client found: xamarinApp / Xamarin App
[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Calling into custom token validator: IdentityServer4.Validation.DefaultCustomTokenValidator
[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Token validation success
{
//Token details omitted here for the sake of simplicity.
}
}
[16:43:12 Information] IdentityServer4.Validation.EndSessionRequestValidator
End session request validation success
{
"ClientId": "xamarinApp",
"ClientName": "Xamarin App",
"SubjectId": "unknown",
"PostLogOutUri": "xamarinformsclients://callback",
"Raw": {
"id_token_hint": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3RjlGQ0VFRTVCMzM4ODkzODZCNjc2MTZCRjZCOTFEMUEwRkRBQjAiLCJ0eXAiOiJKV1QiLCJ4NXQiOiJCX244N3VXek9JazRhMmRoYV9hNUhSb1AyckEifQ.eyJuYmYiOjE1Mjg5MDA5ODYsImV4cCI6MTUyODkwMTI4NiwiaXNzIjoiaHR0cHM6Ly9sYXB0b3AtMW0waW4zMW46NDQzODciLCJhdWQiOiJ4YW1hcmluQXBwIiwibm9uY2UiOiI4YjZjZWRkMDFhMjQ0ZDJmOWY3ZGM4NzZmM2NmZGYwNiIsImlhdCI6MTUyODkwMDk4NiwiYXRfaGFzaCI6IkZualBtd2hiZTNmOVRITjEzM0NSZWciLCJzaWQiOiJkMmJlZTgyYzg0YWY2NGI5ZDUyYmZlNmExNmU1MTNmZiIsInN1YiI6IjI4IiwiYXV0aF90aW1lIjoxNTI4OTAwOTgzLCJpZHAiOiJsb2NhbCIsInVzZXJfaWQiOiIyOCIsInJvbGVfaWQiOiI0IiwibmFtZSI6IlRpbGwgU2F1YmVybWFubiIsImZhbWlseV9uYW1lIjoiU2F1YmVybWFubiIsImFtciI6WyJwd2QiXX0.ZjwL8nuq-WD3D-pXruZtE_I5TyNNO_ZMabz2JiKVnTaTnITwGV5CIJcLcWSpBCOyaSFXKUicAtROeWLReuk_LWoUTKXcX7lyv5VP9-ItBNA13EwgsbhQX7BgS2lbE9fQU7OgGARJcpvPKaT9FabFtEZsNYW9sNeBo-6CUPkYtVH_rjRyLihFi2NlZlkHBc7_oPE0hsjf61QIwyGZEhVXvDXkP_Q9t_Bfr3_QrUF6MfyhzLs0KcMwbtlWUxYw51J8phz7RPUXbbiZ1tG9Ay4DNy8RZbzfI-uFAbrqH7waLo_f5JO15eYc-xICl22ZS_4lW0_MlzP_rq46PnGOwNBqlg",
"post_logout_redirect_uri": "xamarinformsclients://callback"
}
}
Edit As far as I can understand, this probably has to do with my Xamarin Client and Cookies. I found tutorials on how to configure a MVC Client, IDSVR4 and the Cookie Middleware, but nothing regarding native Apps, IDSVR4 and Cookie Middleware.
How is IDSVR4 (or the logout in particular) supposed to work with a non-MVC Client and IdentityModel.OidcClient?
Finally I found the reason. In the QuickstartUI Examples, the Class "AccountConteroller.cs" sets Explicit Expiration only if the user chooses the "remember me" Option. I removed the if condition, and finally the authentication cookie is properly stored, on logout my user is not null anymore, and everything is fine.
class AccountController
...
AuthenticationProperties props = null;
//ALWAYS SET EXPLICIT EXPIRATION, SO COOKIE CAN BE DELETED WHEN LOGGING OUT
//if (AccountOptions.AllowRememberLogin && model.RememberLogin)
//{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
// };

Resources