I'm trying to add Google authentication to my Azure Functions app which will be used from a Svelte static web app (SWA). The SWA uses Google Identity (https://accounts.google.com/gsi/client) to both authenticate and then retrieve an access_token. Authentication is performed using a standard Google Identity sign in button. I've tried One Tap prompt as well with the same result.
google.accounts.id.initialize({
client_id: googleClientId,
callback: handleCredentialResponse,
});
google.accounts.id.renderButton(
button,
{ theme: 'outline', size: 'large' }, // customization attributes
);
User authenticates, works fine and I get a JWT id_token containing name email image etcetera. It's a bit annoying the user has to then again go through the whole process of selecting their account, but I guess that's the Google experience. Once I'm ready to do function calls I then proceed to authorize:
function getAccessToken() {
return new Promise((resolve, reject) => {
const client = google.accounts.oauth2.initTokenClient({
client_id: googleClientId,
scope: "openid",
callback: (response) => {
if (response.access_token) {
resolve(access_token);
} else {
reject(response?.error);
}
},
});
client.requestAccessToken();
});
}
This also works fine, I retrieve an access_token. I then proceed to call an Azure Function with this token in the header:
Authorization: Bearer <ACCESS_TOKEN>
This always results in a 401 response. I have tried setting all functions to anonymous to no effect.
I'm wondering if this has to do with scope. In the Google Console it's only possible to add Google specific scopes, which is why I retrieve an access_token for the openid scope.
I've also tried setting credentials to include since there might be cookies the Easy Auth layer would like to read from the web app to authenticate the user. CORS on the Azure Functions app is configured correctly for the host names used by the web app and Access-Control-Allow-Credentials is enabled on the Function App. This has no effect either.
Wow this was badly documented. After reading the Azure Functions and App Service Authentication blog post it seems an 'authentication token' needs to be retrieved from the functions app itself instead of an 'access token' from Google. After Google identification the id_token from the first step needs to be POSTed to https://<functions_app>/.auth/login/google with the following as body:
{
"id_token": "<id_token>"
}
This in turn returns something as follows:
{
"authenticationToken": "<authenticationToken>",
"user": { "userId": "<sid>" }
}
This authenticationToken then needs be be passed in the header to each function call as follows:
X-ZUMO-AUTH: <authenticationToken>
Edit: it seems this was fully documented, somehow I missed this.
Related
I've previously setup and followed these steps, and got it to work:
https://firebaseopensource.com/projects/firebase/quickstart-js/auth/chromextension/readme/#license
But cloning this extension, but with a different Extension ID, different firebase instance and OAuth/key setup, I've tried to follow the steps at least 3 separate times, but every time it fails at the last step, the login (the consent screen works though)
Upload a fresh dummy extension (without key field in manifest.json) (But it is the exact same code as the working one)
Get the Chrome Extension ID, and the Public Key, no problem
Create OAuth Client ID with the Extension ID, configured consent screen, no problem, the screen shows up and I can click through
Add OAuth & Public Key to manifest.json
Make another OAuth Client ID? (I think this is a duplicate step, because which Client ID should I use? and afaik the whitelisting is optional)
Use chrome.identity to get OAuth token:
export function startAuth(interactive) {
// Request an OAuth token from the Chrome Identity API.
chrome.identity
.getAuthToken({ interactive: !!interactive }, (token) => {
if (chrome.runtime && !interactive) {
console.error('It was not possible to get a token programmatically.');
}
else if (token) {
// Authorize Firebase with the OAuth Access Token.
const credential = firebase.auth.GoogleAuthProvider
.credential(null, token);
firebase.auth()
.signInWithCredential(credential)
.catch((error) => {
// The OAuth token might have been invalidated. Lets' remove it from cache.
if (error.code === 'auth/invalid-credential') {
chrome.identity
.removeCachedAuthToken({ token }, () => {
startAuth(interactive);
});
}
});
}
else {
console.error('The OAuth Token was null');
}
});
}
Note: This code is working with another extensionID/OAuth/key, so the code itself can't be the problem.
There isn't much to change between them, it's really the ExtensionID, the OAuth client ID url, and the public key.
I've followed the steps 3 times, to no avail, every time I get the error auth/invalid-credential. I do get a token though, but that token won't authenticate. How to find out why this is?
It's trying to post to this address, and returning error 400:
POST: https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp?key=xxxxx
Error: INVALID_IDP_RESPONSE : Invalid Idp Response: access_token audience is not for this project
My conclusion
There must be something changed with how to set this up, even though it works with different credentials
The problem was that I didn't create the OAuth in the same project in google cloud console, as the firebase project...
We have a small React Web application with a Fastify backend (but can very easily apply the same concepts to Express js) that feeds the db data to the frontend clients.
Some of the API routes are protected by means of middleware checking the validity of the access token.
Our auth strategy used to be only credentials based, ie using Username and Password.
We have recently implemented authentication by Google and Facebook as well and for the most part, they seem to work well.
We now want to replicate our web application to a mobile application.
I have never written a mobile app and am currently busy learning React-Native.
I managed to implement Google Auth on the IOS app.
Here is the problem. I am not sure how to implement token validation for mobile clients on protected routes using the Google access token. I am however able to successfully implement validation if I use the id token.
My understanding is that validation on protected routes should be done using the access token, otherwise, what is the point of the access token in the first place.
Anyway, my understanding of the entire authentication flow is clearly flawed.
I just need some advice, samples or references to articles that I can read that can clarify the concepts to me.
Just the summary of what I hope to achieve:
a single backend with various (but not all) protected routes
the backend to support credential-based, Google and Facebook authentication strategies
backend middleware to check the validity of the access token
backend should serve both native mobile and web applications.
Sample of a protected route:
fastify.get(
"/protectedroute",
{ preValidation: [fastify.googleverify] }, //<--- Middleware only checking Google validity currently. TODO: Multi-provider middleware to be created
async (request, reply) => {
reply.code(200).send({ message: "authenticated", user: request.user });
}
);
Sample of middleware to check google id_token validity:
fastify.decorate("googleverify", async function (request, reply, done) {
if (!request.raw.headers.authorization) {
return done(new Error("Missing token header"));
}
const bearer = request.raw.headers.authorization;
const token = bearer.split(" ");
try {
const userinfo = await verify(token[1]);
request.user = userinfo;
return done();
} catch (error) {
reply.code(401).send({ message: "Not Authorized", error: error });
}
});
This has been baffling me for hours now, so I have been trying to get EasyAuth working using different providers.
I am using this on Azure Functions, so let's say my function address is
https://xxx.azurewebsites.net
If I want to login into the service using a Google account I send my post request along with token received from Google to the following address
https://xxx.azurewebsites.net/.auth/login/google
This gives me a converted token back.
However if I do the same thing with a Microsoft account using the following details
Request Body:
{ "access_token": "token-string-value" }
Endpoint:
https://xxx.azurewebsites.net/.auth/login/microsoftaccount
It gives me the following error instead of a converted token
401 Unauthorized You do not have permission to view this directory or page.
--
I am using Msal JavaScript library to get my authentication token. Also I am testing these in Postman which makes it easy to understand what the problem is before I deal with the code and other stuff.
-- Update 1.0
This does seem like a bug, as even if I try to navigate to the
https://xxx.azurewebsites.net/.auth/login/microsoftaccount
It shows me the following
This URL works for other providers, Google, Facebook and Twitter. For all of them it redirects the user to the provider's login page.
According to the error page and the address bar contents, the client doesn't exist which could be referring to the application created on Azure to allow my website access the API. But everything has been setup correctly.
It would be helpful if someone from Azure We App Services can take a look at this.
I have created the Application and added the application ID and Secret int eh App Services page.
-- Update 2.0
So after hours of investigation, I managed to get the URL working, shockingly it was due to wrong information given on Azure portal. The link in Authorization and Authentication section of App Service is pointing to a new platform to register applications, which is purely for Azure AD based users.
For the external users to be able to login the application need to be registered in the following portal
https://apps.dev.microsoft.com
After registering the application here, and added the details in the App Service blade, the URL to EasyAuth is working.
However this doesn't resolve my issue. I still need a JavaScript library that gives me valid token which I can pass to EasyAuth endpoint.
Strangely the token taken from MSAL is not valid for Microsoft account. It just gives me the same error that my access is unauthorised. This means I probably need to use a different library to get a different token. I'd appreciate it if still someone can help me with this.
Below is a short sample code I am using to retrieve token and pass it to another function n which call EasyAuth endpoint and post the token along.
var applicationConfig = {
clientID: "xxxx-xxx-xxxx-xxxx",
authority: "https://login.microsoftonline.com/9fc1061d-5e26-4fd5-807e-bd969d857223",
graphScopes: ["user.read"],
graphEndpoint: "https://graph.microsoft.com/v1.0/me"
};
var myMSALObj = new Msal.UserAgentApplication(applicationConfig.clientID, applicationConfig.authority, acquireTokenRedirectCallBack,
{ storeAuthStateInCookie: true, cacheLocation: "localStorage" });
function signIn() {
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
//Login Success
acquireTokenPopupAndCallMSGraph();
}, function (error) {
console.log(error);
});
}
function signOut() {
myMSALObj.logout();
}
function acquireTokenPopupAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
// accessToken
}, function (error) {
console.log(error);
});
}
I managed to find what was causing the problem.
So basically only Live Connect SDK generated tokens are valid on
https://xxx.azurewebsites.net/.auth/login/microsoftaccount
We were using MSAL which was generating tokens valid only on Azure Active Directory. I have been in touch with Azure Support, and have asked them to update the documentation. It currently is very confusing as none of these have been explained in the EasyAuth documentations.
We decided to go with Azure AD B2C, as it's more reliable and turns out cheaper for us.
In case anyone would like to use EasyAuth with Microsoft Account, the following is showing how to get access token from Live SDK
WL.Event.subscribe("auth.login", onLogin);
WL.init({
client_id: "xxxxxx",
redirect_uri: "xxxxxx",
scope: "wl.signin",
response_type: "token"
});
WL.ui({
name: "signin",
element: "signin"
});
function onLogin(session) {
if (!session.error) {
var access_token = session.session.access_token;
mobileClient.login('microsoftaccount', { 'access_token': access_token }, false)
.then(function () {
console.log('TODO - could enable/disable functionality etc')
}, function (error) {
console.log(`ERROR: ${error}`);
});
}
else {
console.log(`ERROR: ${session.error_description}`);
}
}
Reference to
< script src="//js.live.net/v5.0/wl.js">
I would like to use Auth0 to gain access for Google APIs. Was trying to follow the instructions on this post
Currently to trigger Auth0, I used conv.ask(new SignIn()); and app.intent("actions_intent_SIGN_IN", (conv, params, signin) => { ... })
I get a token from const code = conv.user.access.token; in my actions_intent_SIGN_IN.
However, it seems that this token isn't the token that is used to gain an access_token for Google, aka thru POST /oauth/token. Whatever this token is, it seems like it doesn't work for any of the requests except GET /userinfo. On Google Cloud Functions, I get this on my log:
{ error: 'invalid_grant', error_description: 'Invalid authorization code' }
I played around for a bit with Postman and managed to retrieve the (seemingly?) correct authorization code that can be used for POST /oauth/token thru GET /authorize and building my own url like https://[APP NAME].auth0.com/authorize?response_type=code&client_id=[CLIENT ID]&redirect_uri=[REDIRECT URI]. The authorization code appeared in https://[APP NAME].auth0.com/login/callback?code=[CODE HERE]
The issue is - how do I retrieve the code=[CODE HERE] from dialogflow?
You can authenticate with service accounts that use OAuth2.0 -- you can read more about that in general here and they'll save you a network call. You'll also need to find the appropriate Google API scope(s) here. Dialogflow has samples that show how this is done on Github, for example.
const {google} = require('googleapis');
const serviceAccount = { }; // Enter Service Account private key JSON
const client = new google.auth.JWT({
email: serviceAccount.client_email,
key: serviceAccount.private_key,
scopes: ['https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive']
});
I'm trying to create a rest api for a service I'm working on.
The service has two parts to it - the website and the mobile client. Basically, the mobile device keeps its location up to date via the api, the website displays the data via the api.
Seeing as my application only targets Android, I'm hoping to use 'Sign in with Google' as the authentication mechanism for both the mobile and website clients.
The API is using Node.js and Express.js. I'm running into trouble when generating new user accounts though. Seeing as I don't want to trust data from the client, my expected sign up process was something like this:
Through the website:
User visits website, hits 'Sign up with Google'.
User accepts the app request to see their Google details.
Website gets a google auth token back, which it sends to the api.
API contacts google with that auth token to get the user details.
API creates a new user in the database along with my own form of access token.
API returns my own access token to the client for future request signing.
Through the Android app:
User downloads the app, runs and hits 'Sign up with Google'.
User accepts authorisation step presented by google.
App gets a token, which it sends to the API.
API contacts google with that auth token to get the user details.
API realises the user exists and registers this new device with that user.
API returns my own access token to the app for future request signing.
I'm running into a lot of trouble here as soon as the token gets to the server though. Every time I use the token generated, I just get an error 'Invalid Credentials'.
Initially I started to use Passport.js, but what I found was this. In the documentation it states setup happens like so:
passport.use(new GoogleStrategy({
returnURL: 'http://www.example.com/auth/google/return',
realm: 'http://www.example.com/'
},
function(identifier, profile, done) {
User.findOrCreate({ openId: identifier }, function(err, user) {
done(err, user);
});
}));
But when I log the contents of 'identifier' it is actually something like
https://www.google.com/accounts/o8/id?id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I assume the ID is something unique to me but I can't seem to discover exactly what it is. Furthermore I don't know if it is time-limited or will last forever. As a final problem, I don't even know if I can get that same value when signing up via Android because I don't know where the value comes from. It's not the kind of API access token that I was expecting. When I output the contents of profile, it's just my name and email address - nothing that I can use for contacting the Google API with to verify the user.
The above solution I don't like anyway because it means the server hosting the client site has to make an api request in order to pass the id to the api. Or it sends the id details to the client so that it can pass them on to the api server. Or, the website server puts it into the api database, which is also a bad solution.
So next I figured I would use the javascript library from the Google sign in docs. I have something like this:
Website Client:
<script type="text/javascript">
function createUser(token)
{
$.ajax({
url:"http://api.example.com/user",
dataType: 'jsonp',
data: 'token='+token,
success:function(json){
alert("Success: "+json);
},
error:function(jqXHR, textStatus, errorThrown){
alert("Error "+textStatus+" "+errorThrown);
}
});
}
function signinCallback(authResult)
{
if(authResult['access_token'])
{
document.getElementById('signinButton').setAttribute('style', 'display: none');
alert('RES: '+JSON.stringify(authResult));
createUser(authResult['access_token']);
}
else if(authResult['error'])
{
alert('There was an error: ' + authResult['error']);
}
}
</script>
Node API user handling function:
function(req, res)
{
var callback = req.query.callback;
if(callback == null)
{
res.send("{valid:false}");
}
else
{
var token = req.query.token;
if(token == null)
{
res.send("{valid:false}");
}
else
{
var oauth2Client = new OAuth2Client('xxxxxx', 'xxxxxx', '');
oauth2Client.credentials = {
access_token: token
};
googleapis
.discover('plus', 'v1')
.execute(function(err, client){
if(client == null)
{
console.log("Client is null");
}
else
{
var request1 = client.plus.people.get({ userId: 'me' })
.withApiKey('xxxxxx');
request1.execute(function(err, result){
console.log("Result: " + (err ? err.message : result.displayName));
});
}
});
res.set('Content-Type', 'text/javascript');
res.send(callback+"({ret:'User Test'});");
}
}
}
This works fine on the client side - I see the alert box with my access token and some other details. The trouble is that when I call the google api functions on my api server for getting the user details, I get 'Invalid Credentials' returned. I assume this is because I generated the access token in javascript for a website and I'm using it from somewhere else.
So, that pretty much leaves me out of ideas. Is there an easy way to achieve this that I'm missing? I just want to be able to generate a token from a website and from an Android app that I can use on the server for validating the user's details. The generated token doesn't even need to work on the website or the Android app, just from the api server. The API server can't do the process of directing the user to Google for authorisation though because the user doesn't directly interact with it.