How I can get proper public keys for apple signin - jwk

I'm trying to implement apple signin and get a token from appleid provider and i've found out the jwks-client can do the trick but I can't get necessary keys. For some reason it returns nothing as a public key. What uri I need to use to get public keys?
const kid = JSON.parse(header).kid;
const jwksClient = require('jwks-client');
const client = jwksClient({
jwksUri: // what do i need to use?
});
client.getSigningKey(kid, (err, key) => {
// no key returned
});

Try using https://appleid.apple.com/auth/keys as stated on the apple dev portal documentation.

Related

access to places api from a cloud function

as part of my firebase app, I'm using a cloud function to get data from google places API.
for some reason, I'm getting 403 errors when trying to retrieve data, even though the service account I'm using is the default one (App Engine default service account with Editor role) which seems to exist on the API credentials list and also on the specific cloud function I'm using.
here's the code I'm using to retrieve data from the API -
class GoogleMapsRestApiClass {
client = new Client({});
getPlaceInfo(placeId: string) {
return this.client.placeDetails({
params: {
place_id: placeId,
fields: ["name", "rating", "geometry", "photo"],
key: environment.googleMapsJsApi.apiKey
}
} as PlaceDetailsRequest);
}
}
export const GoogleMapsRestApi = new GoogleMapsRestApiClass();
and the cloud function itself -
export const place = functions.https.onRequest(async (request, response) => {
const placeId = request.query.place_id as string;
const resp: AxiosResponse = await GoogleMapsRestApi.getPlaceInfo(placeId);
const result = resp.data.result;
response.send({result});
});
any ideas what I'm missing here?
Update -
if I'm not restricting the API key I do manage to retrieve the data (restricted it to my host address).
how should I protect the API key being used by cloud function?

Call Cloud Run from Cloud Function: IAM Authentication

I've deployed a small HTTP endpoint via Google Cloud Run. It is working fine when I turn off the authentication.
I now want to turn it on so that it is only callable by my Firebase Cloud Function. If I understand it right, I just have to add the correct service account mail address in the IAM settings of the Cloud Run as "Cloud Run invoker".
But which address is the correct one?
I've tried all addresses that I have found in Firebase Console -> Project Settings -> Service Accounts.
I think you can check the specific firebase function. In the UI, the service account used should be listed.
By default, GCF functions all use <project_id>#appspot.gserviceaccount.com
Thanks to #AhmetB - Google and #whlee's answer I got it working. Basically it is enough adding an Authorization Bearer token to the request, which you can get from a special endpoint: https://cloud.google.com/run/docs/authenticating/service-to-service#nodejs
Then you just have to add the service account of the function to the IAM list of the Cloud Run container: <project_id>#appspot.gserviceaccount.com
The nodejs example is using the deprecated request library, so here is my version using axios:
const getOAuthToken = async (receivingServiceURL: string): Promise<string> => {
// Set up metadata server request
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const uri = metadataServerTokenURL + receivingServiceURL;
const options = {
headers: {
'Metadata-Flavor': 'Google'
}
};
return axios.get(uri, options)
.then((res) => res.data)
.catch((error) => Promise.reject(error));
}
Then you can just use the token in the actual request:
const url = `...`;
const token = await getOAuthToken(url);
axios.post(url, formData, {
headers: {
Authorization: `Bearer ${token}`,
}
}).then(...).catch(...);
#luhu 's answer was really helpful. I'd like to add just one note for those whose are willing to test with the emulators locally first. The metadata server (which is actually http://metadata.google.internal now) as they state
does not work outside of Google Cloud, including from your local machine.
As a workarund, you can use the google-auth-library and then get the token directly if you prefer sticking with axios. Remember to set the GOOGLE_APPLICATION_CREDENTIALS env variable pointing to a service account secret first as it's the only way to make it work (I've tested setting the credential field during admin.initializeApp() but didn't seem to like it).
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
const url_origin = '....'
const client = await auth.getIdTokenClient(url_origin);
const token = (await client.getRequestHeaders()).Authorization;
const url = '....'
const response = await axios.get(
url,
{
headers: {
Authorization: token,
},
}
);

Google cloud function returning 204 status when accessing realtime database

I have a website for testing purposes hosted via firebase, storing client information on a realtime database which needs to be accessed later. When I do this via a single html document with a script that accesses my reatime database I am able to find information successfuly, but when I copied and pasted that same logic into a cloud function it did not work. I have tried everything I can think of and now when I run the function it executes twice (I am not sure why). The first execution finishes with a http 204 status (no content found). The second execution returns http 500 internal service error. When I checked the logs on firebase it says the error was because "accounts.getValue() is not a function". I think what is happening is on the first execution the function is unable to locate accounts and it executes again without trying to find the accounts, which might be why it can't run accounts.getValue()
I guess my main question is why is my function unable to locate accounts?
geturl is the function I am having trouble with
The structure of my realtime database is
database name
-accounts
-some data
-more data
-more account data
-ActiveQRs
-some data...
My index.js file for cloud functions is
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors')({origin: true});
var firebase = require("firebase");
var admin = require("firebase-admin");
require("firebase/auth");
require("firebase/database");
//require("firebase/firestore");
//require("firebase/messaging");
require("firebase/functions");
var serviceAccount = require("./serviceKey.json");
// Initialize the app with a service account, granting admin
//privileges
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://databaseName.firebaseio.com"
});
const displayqr = express();
const geturl = express();
displayqr.get('/displayqr', (request, response) => {
console.log("response sent");
response.send("testio/qrdisplay.html");
});
exports.displayqr = functions.https.onRequest(displayqr);
exports.geturl = functions.https.onCall((email) => {
const mail = email.toString();
var result = "";
result = result + mail;
var accounts =
admin.database().ref("livsuiteform/accounts");
result = (accounts.getValue());
accounts.orderByKey().on("value", function(snapshot) {
snapshot.forEach(function(data) {
if (data.child("Email").val() == mail) {
var firstName = data.child("FirstName").val();
var lastName = data.child("LastName").val();
result = firstname;
result = "if loop entered";
} // end if
// return "name not found";
}); // end for each
}); // end order by
return result;
});
TLDR; follow this tutorial on how to build and deploy callable functions for your mobile app.
There are multiple reasons for why your functions aren't working as you expect.
You are including the client-side version of Firebase (var firebase = require("firebase");). You shouldn't use or even require the client-side version. Instead just use Firebase Admin (docs) to access any data. If you need certain user permissions when accessing the DB from the Admin SDK, here is a good example of how to achieve that (Scroll down to "You can still perform user-authorized changes...").
You have mixed different Admin SDK references. getValue() is part of the Admin SDK for Java. You should use the JavaScript equivalent val(). Also, in your code, accounts is a Reference and not a DataSnapshot.
You aren't returning your Promise's. This can be a source of inconsistency in your function execution later SO Question.
You aren't returning anything from your initial function. If you don't return anything, then nothing will get returned to your app. The solution is the same as 3's solution: return your Promise.
You shouldn't use on in Firebase Functions. You should use once. The difference is that on doesn't return a Promise while once does. It returns a function that is used to detach the listener.
I know this is a lot of bullet points and pointing out problems in your code, but I just didn't want give a shallow answer which resulted in you asking another question and waiting another ~2 hours (at the time of writing) for an answer.
I hope this helps!
Code
exports.geturl = functions.https.onCall((email) => {
const mail = email.toString();
var result = "";
result = result + mail;
var accounts = admin.database().ref("livsuiteform/accounts");
return accounts.orderByKey().once("value")
.then(function (snapshot) {
snapshot.forEach(function (data) {
if (data.child("Email").val() == mail) {
var firstName = data.child("FirstName").val();
var lastName = data.child("LastName").val();
result = firstName;
result = "if loop entered";
} // end if
// return "name not found";
}); // end for each
return result;
}); // end order by
});

Call Google Play Developer API from Firebase Functions

I am trying to develop a server-side validation of my users' in-app purchases and subscriptions as recommended, and I want to use Firebase Functions for that. Basically it has to be an HTTP trigger function that receives a purchase token, calls the Play Developer API to verify the purchase, and then does something with the result.
However, calling many of the Google APIs (including Play Developer API) requires non-trivial authorization. Here's how I understand the required setup:
There has to be a GCP project with Google Play Developer API v2 enabled.
It should be a separate project, since there can be only one linked to Play Store in the Google Play Console.
My Firebase Functions project must somehow authenticate to that other project. I figured that using a Service Account is most suitable in this server-to-server scenario.
Finally, my Firebase Functions code must somehow obtain authentication token (hopefully JWT?) and finally make an API call to get a subscription status.
The problem is that absolutely no human-readable documentation or guidance on that is existent. Given that ingress traffic in Firebase is included in the free plan (so I assume they encourage using Google APIs from Firebase Functions), that fact is pretty disappointing. I've managed to find some bits of info here and there, but having too little experience with Google APIs (most of which required simply using an api key), I need help with putting it together.
Here's what I figured out so far:
I got a GCP project linked to the Play Store and with the API enabled. For some reason though, trying to test it in APIs Explorer results in an error "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console".
I made a Service Account and exported a JSON key, which contains the key to produce a JWT.
I also set up read permissions for that Service Account in Play Console.
I found a Node.JS client library for Google APIs, which is in alpha and has very sparse documentation (e.g. there's no obvious documentation on how to authenticate with JWT, and no samples on how to call the android publisher API). At the moment I'm struggling with that. Unfortunately I'm not super-comfortable with reading JS library code, especially when the editor doesn't provide the possibility to jump to highlighted functions' sources.
I'm pretty surprised this hasn't been asked or documented, because verifying in-app purchases from Firebase Functions seems like a common task. Has anyone successfully done it before, or maybe the Firebase team will step in to answer?
I figured it out myself. I also ditched the heavyweight client library and just coded those few requests manually.
Notes:
The same applies to any Node.js server environment. You still need the key file of a separate service account to mint a JWT and the two steps to call the API, and Firebase is no different.
The same applies to other APIs that require authentication as well — differing only in scope field of the JWT.
There are a few APIs that don't need you to exchange the JWT for an access token — you can mint a JWT and provide it directly in Authentication: Bearer, without a round trip to OAuth backend.
After you've got the JSON file with the private key for a Service Account that's linked to Play Store, the code to call the API is like this (adjust to your needs). Note: I used request-promise as a nicer way to do http.request.
const functions = require('firebase-functions');
const jwt = require('jsonwebtoken');
const keyData = require('./key.json'); // Path to your JSON key file
const request = require('request-promise');
/**
* Exchanges the private key file for a temporary access token,
* which is valid for 1 hour and can be reused for multiple requests
*/
function getAccessToken(keyData) {
// Create a JSON Web Token for the Service Account linked to Play Store
const token = jwt.sign(
{ scope: 'https://www.googleapis.com/auth/androidpublisher' },
keyData.private_key,
{
algorithm: 'RS256',
expiresIn: '1h',
issuer: keyData.client_email,
subject: keyData.client_email,
audience: 'https://www.googleapis.com/oauth2/v4/token'
}
);
// Make a request to Google APIs OAuth backend to exchange it for an access token
// Returns a promise
return request.post({
uri: 'https://www.googleapis.com/oauth2/v4/token',
form: {
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': token
},
transform: body => JSON.parse(body).access_token
});
}
/**
* Makes a GET request to given URL with the access token
*/
function makeApiRequest(url, accessToken) {
return request.get({
url: url,
auth: {
bearer: accessToken
},
transform: body => JSON.parse(body)
});
}
// Our test function
exports.testApi = functions.https.onRequest((req, res) => {
// TODO: process the request, extract parameters, authenticate the user etc
// The API url to call - edit this
const url = `https://www.googleapis.com/androidpublisher/v2/applications/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}`;
getAccessToken(keyData)
.then(token => {
return makeApiRequest(url, token);
})
.then(response => {
// TODO: process the response, e.g. validate the purchase, set access claims to the user etc.
res.send(response);
return;
})
.catch(err => {
res.status(500).send(err);
});
});
These are the docs I followed.
I think I found a slightly quicker way to do this... or at least... more simply.
To support scaling and keep index.ts from growing out of control... I have all the functions and globals in the index file but all the actual events are handled by handlers. Easier to maintain.
So here's my index.ts (I heart type safety):
//my imports so you know
import * as functions from 'firebase-functions';
import * as admin from "firebase-admin";
import { SubscriptionEventHandler } from "./subscription/subscription-event-handler";
// honestly not 100% sure this is necessary
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: 'dburl'
});
const db = admin.database();
//reference to the class that actually does the logic things
const subscriptionEventHandler = new SubscriptionEventHandler(db);
//yay events!!!
export const onSubscriptionChange = functions.pubsub.topic('subscription_status_channel').onPublish((message, context) => {
return subscriptionEventHandler.handle(message, context);
});
//aren't you happy this is succinct??? I am!
Now... for the show!
// importing like World Market
import * as admin from "firebase-admin";
import {SubscriptionMessageEvent} from "./model/subscription-message-event";
import {androidpublisher_v3, google, oauth2_v2} from "googleapis";
import {UrlParser} from "../utils/url-parser";
import {AxiosResponse} from "axios";
import Schema$SubscriptionPurchase = androidpublisher_v3.Schema$SubscriptionPurchase;
import Androidpublisher = androidpublisher_v3.Androidpublisher;
// you have to get this from your service account... or you could guess
const key = {
"type": "service_account",
"project_id": "not going to tell you",
"private_key_id": "really not going to tell you",
"private_key": "okay... I'll tell you",
"client_email": "doesn't matter",
"client_id": "some number",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "another url"
};
//don't guess this... this is right
const androidPublisherScope = "https://www.googleapis.com/auth/androidpublisher";
// the handler
export class SubscriptionEventHandler {
private ref: admin.database.Reference;
// so you don't need to do this... I just did to log the events in the db
constructor(db: admin.database.Database) {
this.ref = db.ref('/subscriptionEvents');
}
// where the magic happens
public handle(message, context): any {
const data = JSON.parse(Buffer.from(message.data, 'base64').toString()) as SubscriptionMessageEvent;
// if subscriptionNotification is truthy then we're solid here
if (message.json.subscriptionNotification) {
// go get the the auth client but it's async... so wait
return google.auth.getClient({
scopes: androidPublisherScope,
credentials: key
}).then(auth => {
//yay! success! Build android publisher!
const androidPublisher = new Androidpublisher({
auth: auth
});
// get the subscription details
androidPublisher.purchases.subscriptions.get({
packageName: data.packageName,
subscriptionId: data.subscriptionNotification.subscriptionId,
token: data.subscriptionNotification.purchaseToken
}).then((response: AxiosResponse<Schema$SubscriptionPurchase>) => {
//promise fulfilled... grandma would be so happy
console.log("Successfully retrieved details: " + response.data.orderId);
}).catch(err => console.error('Error during retrieval', err));
});
} else {
console.log('Test event... logging test');
return this.ref.child('/testSubscriptionEvents').push(data);
}
}
}
There are few model classes that help:
export class SubscriptionMessageEvent {
version: string;
packageName: string;
eventTimeMillis: number;
subscriptionNotification: SubscriptionNotification;
testNotification: TestNotification;
}
export class SubscriptionNotification {
version: string;
notificationType: number;
purchaseToken: string;
subscriptionId: string;
}
So that's how we do that thing.

Meteor patrickml:braintree Authentication Error

This command meteor add patrickml:braintree was run in a Meteor app directory.
In the client.main.js:
A squiggly line Under the variable braintree and the IDE says "unresolved variable or type".
Template.payment.onRendered(function () {
Meteor.call('getClientToken', function (error, clientToken) {
if (error) {
console.log(error); //<---- always prints out
} else {
//vvvvvvvvv
braintree.setup(clientToken, "dropin", {
container: "payment-form", // Injecting into <div id="payment-form"></div>
onPaymentMethodReceived: function (response) {
var nonce = response.nonce;
console.log(nonce);
}
});
}
});
});
In the server code below, clientId is always undefined.
//server/main.js
'getClientToken': function (clientId) {
console.log(clientId); //<--------- undefined
let generateToken = Meteor.wrapAsync(gateway.clientToken.generate, gateway.clientToken);
let options = {};
if (clientId) {
options.clientId = clientId;
}
let response = generateToken(options);
return response.clientToken;
}
And the server console prints out:
Exception while invoking method 'getClientToken' authenticationError: Authentication Error
Any idea what is wrong and how to fix it? thx
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact support.
When you configure your gateway object, make sure that you use the API credentials documented in your Sandbox Control Panel. Here's how you can find them:
Log into the sandbox Control Panel
Navigate to Account > My user
Under API Keys, Tokenization Keys, Encryption Keys, click View Authorizations
If no API keys appear, click Generate New API Key
Click View under the Private Key column to see your public and private keys, merchant ID, and environment
When you have them, use them to configure your gateway object. For example:
var braintree = require("braintree");
var gateway = braintree.connect({
environment: braintree.Environment.Sandbox,
merchantId: "replaceWithYourMerchantId",
publicKey: "replaceWithYourPublicKey",
privateKey: "replaceWithYourPrivateKey"
});

Resources