iOS & node.js: how to verify passed access token? - node.js

I have an iOS which uses OAuth and OAuth2 providers (Facebook, google, twitter, etc) to validate a user and provide access tokens. Apart from minimal data such as name and email address, the app doesn't uses these services for anything except authentication.
The app then sends the access token to a server to indicate that the user is authenticated.
The server is written in Node.js and before doing anything it needs to validate the supplied access token against the correct OAuth* service.
I've been looking around, but so far all the node.js authentication modules I've found appear to be for logging in and authenticating through web pages supplied by the server.
Does anyone know any node.js modules that can do simple validation of a supplied access token?

To the best of my knowledge (and as far as I can tell from reading the specifications) the OAuth and OAuth 2 specs do not specify a single endpoint for access token validation. That means you will need custom code for each of the providers to validate an access token only.
I looked up what to do for the endpoints you specified:
Facebook
It seems others have used the graph API's 'me' endpoint for Facebook to check if the token is valid. Basically, request:
https://graph.facebook.com/me?access_token={accessToken}
Google
Google have a dedicated debugging endpoint for getting access token information, with nice documentation, too. Basically, request:
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={accessToken}
However, they recommend that you don't do this for production:
The tokeninfo endpoint is useful for debugging but for production
purposes, retrieve Google's public keys from the keys endpoint and
perform the validation locally. You should retrieve the keys URI from
the Discovery document using the jwks_uri metadata value. Requests to
the debugging endpoint may be throttled or otherwise subject to
intermittent errors.
Since Google changes its public keys only infrequently, you can cache
them using the cache directives of the HTTP response and, in the vast
majority of cases, perform local validation much more efficiently than
by using the tokeninfo endpoint. This validation requires retrieving
and parsing certificates, and making the appropriate cryptographic
calls to check the signature. Fortunately, there are well-debugged
libraries available in a wide variety of languages to accomplish this
(see jwt.io).
Twitter
Twitter doesn't seem to have a really obvious way to do this. I would suspect that because the account settings data is pretty static, that might be the best way of verifying (fetching tweets would presumably have a higher latency?), so you can request (with the appropriate OAuth signature etc.):
https://api.twitter.com/1.1/account/settings.json
Note that this API is rate-limited to 15 times per window.
All in all this seems trickier than it would first appear. It might be a better idea to implement some kind of session/auth support on the server. Basically, you could verify the external OAuth token you get once, and then assign the user some session token of your own with which you authenticate with the user ID (email, FB id, whatever) on your own server, rather than continuing to make requests to the OAuth providers for every request you get yourself.

For google in production, install google-auth-library (npm install google-auth-library --save) and use the following:
const { OAuth2Client } = require('google-auth-library');
const client = new OAuth2Client(GOOGLE_CLIENT_ID); // Replace by your client ID
async function verifyGoogleToken(token) {
const ticket = await client.verifyIdToken({
idToken: token,
audience: GOOGLE_CLIENT_ID // Replace by your client ID
});
const payload = ticket.getPayload();
return payload;
}
router.post("/auth/google", (req, res, next) => {
verifyGoogleToken(req.body.idToken).then(user => {
console.log(user); // Token is valid, do whatever you want with the user
})
.catch(console.error); // Token invalid
});
More info on Authenticate google token with a backend server, examples for node.js, java, python and php can be found.
For Facebook, do an https request like:
const https = require('https');
router.post("/auth/facebook", (req, res, next) => {
const options = {
hostname: 'graph.facebook.com',
port: 443,
path: '/me?access_token=' + req.body.authToken,
method: 'GET'
}
const request = https.get(options, response => {
response.on('data', function (user) {
user = JSON.parse(user.toString());
console.log(user);
});
})
request.on('error', (message) => {
console.error(message);
});
request.end();
})

In production for google you can use:
https://www.npmjs.com/package/google-auth-library
const ticket = client.verifyIdToken({
idToken: ctx.request.body.idToken,
audience: process.env.GOOGLE_CLIENTID
})

To get info about token from Google use, be careful vith version api
https://www.googleapis.com/oauth2/v3/tokeninfo?access_token={accessToken}

Related

Correct security implementation strategy for multi-provider authentication and api route protection with Nodejs

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 });
}
});

Can an open id connect id token be used to authenticate to an api

I am building a mern application.
the backend built using express exposes an api which users can create data and access the data they have created.
I want to allow users to sign in with google and get authorization to create and access the resources on this api which i control (not on google apis).
I keep coming across oauth 2 / open id connect articles stating that an Id token is for use by a client and a access token provided by a resource server should be used to get access to an api.
e.g. https://auth0.com/blog/why-should-use-accesstokens-to-secure-an-api/
the reason stated for this is that the aud property on the id token wont be correct if used on the api.
I realise that some sources say: that if the spa and api are served from same server and have same client id and therefore audience I can use and id token to authenticate to the api, but I am looking to understand what I can do when this is not the case?
I feel using oauth2 for authorization is overkill for my app and I cant find any information about how to use open id connect to authenticate to my api.
Surely when you sign in to Auth0 authourization server using google it is just requesting an open id connect id token from google?
I am wondering if using Authorization Code Grant flow to receive an id token on the api server would allow me to authenticate a user to my api?
in this case would the api server be the client as far as open id connect is concerned and therefore the aud value would be correct?
I can generate an url to visit the google oauth server using the node googleapis library like so:
const { google } = require("googleapis");
const oauth2Client = new google.auth.OAuth2(
'clientid','clientsecret',
"http://localhost:3000/oauthcallback",//this is where the react app is served from
);
const calendar = google.calendar({ version: "v3", auth: oauth2Client });
const scopes = ["openid"];
const url = oauth2Client.generateAuthUrl({
// 'online' (default) or 'offline' (gets refresh_token)
access_type: "offline",
// If you only need one scope you can pass it as a string
scope: scopes,
});
async function getUrl(req, res) {
console.log(url)
res.status(200).json({
url,
});
}
and use the following flow.
You are not supposed to access any API's using the ID-Token. First of all the life-time of the ID-token is very short, typically like 5 minutes.
You should always use the access-token to access API's and you can using the refresh token get new access-tokens. The ID-token you can only get one time and you use that to create the local user and local cookie session.
If you are using a SPA application, you should also consider using the BFF pattern, to avoid using any tokens in the SPA-Application
see The BFF Pattern (Backend for Frontend): An Introduction
I agree with one of the commenters that you should follow the principle of separation of concern and keep the authorization server as a separate service. Otherwise it will be a pin to debug and troubleshoot when it does not work.

Firebase ID token has incorrect "aud" (audience) claim when calling from Endpoint connected to Google Functions

I am using Google Endpoints as an API gateway which is running in a Google Run container service. The API path points to a Google Function (node js). The calls to the API gateway are from a web application (viz. browser).
One of the paths is: /login which authenticates a user in firebase using the firebase.auth().signInWithEmailAndPassword method. I get the token Id of the user and send it back in the response header (authentication bearer) back to the browser. This works as expected.
When other Requests are made (e.g /check) to the endpoint the token (in the header) is included. I wanted to check the validity of the token using the Firebase Admin method before processing any requests. The code in the Google Function that does this for one of the routes is as follows:
...
const decodeIdToken = async (req, res, next) => {
// Read the ID Token from the Authorization header.
const idToken = req.headers.authorization.split('Bearer ')[1];
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
req.decodedToken = decodedIdToken;
next();
return;
} catch (error) {
return res.status(403).json({
status: 'failure',
data: null,
error: error.message
});
}
};
// use decodeIdToken as middleware
app.post('/check', decodeIdToken, (req, res, next) => {
return res.json({
status: 'success',
data: req.decodedToken,
error: null
});
});
When I call (via Postman ) the routes by directly calling the Google Function trigger both the routes work. However, when I call the Google Endpoints which point to the Google Function I receive the following error while using the Firebase Admin object:
Firebase ID token has incorrect \"aud\" (audience) claim. Expected \"PROJECT-ID\" but got \"https://us-central1-PROJECT-ID.cloudfunctions.net/FUNCTION-NAME\". Make sure the ID token comes from the same Firebase project as the service account used to authenticate this SDK. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token
When setting the Firebase Admin object in NodeJs I tried the following:
const admin = require('firebase-admin');
admin.initializeApp();
as well as
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://PROJECT-ID.firebaseio.com"
});
Use the X-Apigateway-Api-Userinfo Header
The header's value is the base64 encoded payload of the JWT. There's no need to reverify as API Gateway already verified the token for you and has made the contents available for your use.
Example for Node.js devs:
Buffer.from(req.header("x-apigateway-api-userinfo"), "base64").toString();
If for whatever reason you do need access to the original JWT, it is available in the X-Forwared-Authorization header.
Unnecessary Extra Credit:
To explain the error, the reason you are getting the wrong Audience claim is because the JWT you are trying to verify is a different JWT generated by API Gateway. The original Authorization Header has been replaced with this JWT. Why? It is telling Cloud Functions "Hey Cloud Function, it's me API Gateway that's calling you and here's a signed JWT to prove it". Hence API Gateway's audience ends up being the Cloud Function resource url whereas Firebase's audience is the Project the Firebase sits in.
Just another example of weird inconveniences due to Google's implementation if you ask me; they could have definitely left the Auth header untouched and had API Gateway use a different header, but beggars can't be choosers. 🤷‍♂️
Reference API Gateway Documentation:
Receiving authenticated results in your API
API Gateway usually forwards all headers it receives. However, it overrides the original Authorization header when the backend address
is specified by x-google-backend in the API config.
API Gateway will send the authentication result in the X-Apigateway-Api-Userinfo to the backend API. It is recommended to use
this header instead of the original Authorization header. This header
is base64url encoded and contains the JWT payload.
The following worked does not work (see comment below):
In the openapi-functions.yaml add the security defintion as recommended by the docs
securityDefinitions:
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
# Replace YOUR-PROJECT-ID with your project ID
x-google-issuer: "https://securetoken.google.com/YOUR-PROJECT-ID"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: "YOUR-PROJECT-ID"
Then, against the path (/check in my case), add the security section as below:
/check:
post:
...
x-google-backend:
....
....
security:
- firebase: []
....
Refer to: https://cloud.google.com/endpoints/docs/openapi/authenticating-users-firebase
There isn't problem with your admin-sdk settings, it's the idToken which is actually a jwt token retured as idToken while sign in using firebase.
Your problem is you are trying to use the JWT token returned as idToken by one of the auth() functions like firebase.auth().signInWithEmailAndPassword These do return a JWT token, however the auth claims will likely be wrong and won't pass verification by verifyIdToken. Firebase tech support confirmed this.
You have to use the firebase.auth().currentUser.getToken() function. That token will pass verification.
const idToken=await firebase.auth().currentUser.getToken()

How to programmatically login to Facebook oauth2 api

Background:
I have written a Node.JS script that successfully connects to the Facebook Graph API through my facebook app. I can read data when I give it an oauth access_token, I want this script to run on my server every night to store some data. I have done a lot of research of both the facebook api, oauth and similar questions on stack overflow. I am searching the /search/?type=event&q=query endpoint
Problem:
However, Facebook returns a 60 day access_token through the oauth2 login process that required me to create an express server that simply initiates the oauth2 process, allows the user to login, and receives the access_token code and I am storing it.
I want the script to save data so that my server can provide access to updated data every day. I don't want to have to remember to login to generate the key once every 60 days.
Question:
Is there anyway to receive a oauth2 access_token without setting up an http or express server?
More importantly, how do I get the access_token without manually having to running that server every ~60 days.
Code:
The Module I am using requires the access_token and client_secret
fs.readFile('./facebookAuthServer/oauth.txt', function read(err, data) {
if (err) {
throw err;
}
fbNode.setAuthorization({token: data, clientSecret: authSettings.clientSecret});
// Use the auth for next call
fbNode.fetchItems(displayItems);
});
Is there some way to spoof headers? or could I use a short lived access token and refresh it? Anyway to refresh a 60 day token? Has anyone created a server side implementation of Oauth2 that does not require visiting the FB login more than the first time?
Here is how you can refresh your own access token using Grant and request.
First you need an OAuth client server up and running:
var express = require('express')
var session = require('express-session')
var Grant = require('grant-express')
var grant = new Grant({
server:{host:'dummy.com:3000', protocol:'http'},
facebook:{
key:'[APP_ID]',
secret:'[APP_SECRET]',
scope:['user_about_me','user_birthday'],
callback:'/callback'
}
})
var app = express()
app.use(session({secret:'very secret'}))
app.use(grant)
app.get('/callback', function (req, res) {
res.end(JSON.stringify(req.query))
})
app.listen(3000, function () {
console.log('Oh Hi', 3000)
})
Next you need an HTTP client that will simulate the browser request:
var request = require('request')
request.get({
uri:'http://dummy.com:3000/connect/facebook',
headers:{
'user-agent':'Mozilla/5.0 ...',
cookie:'datr=...; lu=...; p=-2; c_user=...; fr=...; xs=...; ...'
},
jar:request.jar(),
json:true
}, function (err, res, body) {
if (err) console.log(err)
console.log(body)
})
How you use it:
Register OAuth app on Facebook and set your Site URL (I'm assuming http://dummy.com:3000)
Add 127.0.0.1 dummy.com to your hosts file
Configure and start the server from above
Navigate to dummy.com:3000 in your browser
Open up the Developer Tools and navigate to the Network tab, make sure Preserve log is checked
Navigate to http://dummy.com:3000/connect/facebook and authenticate as usual
Take a look at the authorize request in the Network tab and copy the relevant headers to the HTTP client example (the user-agent and the cookie)
Run the HTTP client code (that's the code you are going to execute from time to time, the server should be running as well)
Resources:
Introduction about how to use Grant
Grant documentation
Request documentation - see the options section
Module:
I wrapped the above code into a module https://github.com/simov/facebook-refresh-token
Start with reading the docs, it's all described there, instead of guessing:
https://developers.facebook.com/docs/facebook-login/access-tokens
https://developers.facebook.com/docs/facebook-login/access-tokens#refreshtokens
https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.4#login
It's somehow a pity that you don't write WHAT you want to query via the Graph API, because depending on that you could either use a non-expiring page access token or an app access token, which also doesn't have to be renewed, instead of an user access token.
There's no way to automatically extend the long-lived access token. The user must visit your app again.

Oauth2 flow without redirect_uri

I am creating an Android/iOS app which communicates with a Node.js server and would like to identify them securely on my server using Google (and/or Facebook) and OAuth2. I've looked at the following documentation: https://developers.google.com/+/web/signin/server-side-flow
I do not need authorization, I only need authentication (I only want to make sure that the person calling my Node.js service is the person they say they are). To achieve this, if I understand properly, I have to let the user log in using Google on the client side, this will give them an authorization_code which they can then give to my server. My server can then exchange that code for an access_token, and therefore retrieve information about the user. I am then guaranteed that the user is the person they say they are.
The Google documentations (link above) says: "In the Authorized redirect URI field, delete the default value. It is not used for this case.", however, for my server to exchange the authorization_code for an access_token, it needs to provide a redirect_uri, am I missing something?
The redirect_uri is useless for Unity games, for instance (since logging in with Google simply opens a new "window", which is closed when logged in, no redirection involved).
TL;DR
How do you use OAuth2 to authenticate users between my client and my server without redirection?
TL;DR How do you use OAuth2 to authenticate users between my client and my server without redirection?
You can't. OAuth requires that the user is directed to an authorization (and possibly login) screen, and then redirected back to your app.
EDIT 20/12/22. See comment below regarding latest status
Have you looked at this documentation? https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi
Choosing a redirect URI
When you create a client ID in the Google Developers Console, two
redirect_uris are created for you: urn:ietf:wg:oauth:2.0:oob and
http://localhost. The value your application uses determines how the
authorization code is returned to your application.
http://localhost
This value signals to the Google Authorization Server that the
authorization code should be returned as a query string parameter to
the web server on the client. You may specify a port number without
changing the Google Developers Console configuration. To receive the
authorization code using this URL, your application must be listening
on the local web server. This is possible on many, but not all,
platforms. If your platform supports it, this is the recommended
mechanism for obtaining the authorization code.
I had this problem and it took me ages to find the "postmessage" solution that Nepoxx mentions in the comments of the accepted answer here.
For clarification, here's what worked for me.
Follow steps 1-6 here: https://developers.google.com/identity/sign-in/web/server-side-flow
Install googleapis library npm install --save googleapis
For the server-side token exchange do this:
var googleapis = require('googleapis');
var OAuth2 = googleapis.auth.OAuth2;
var oauth2Client = new OAuth2(
GOOGLE_SSO_CLIENT_ID,
GOOGLE_SSO_CLIENT_SECRET,
'postmessage' // this is where you might otherwise specifiy a redirect_uri
);
oauth2Client.getToken(CODE_FROM_STEP_5_OF_INSTRUCTIONS, function(err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
});
The redirect_uri can be a URL with a custom URL scheme for which the client registered a handler. This is described here: What's a redirect URI? how does it apply to iOS app for OAuth2.0?. It is not so much about "redirecting" it is about a callback endpoint to your app.
And it become really easy if you use VueJS with https://github.com/guruahn/vue-google-oauth2
Client side
import GAuth from 'vue-google-oauth2'
Vue.use(GAuth, {
clientId: 'xxxxxxx.apps.googleusercontent.com',
scope: 'profile',
})
async signWithGoogle() {
const code = await this.$gAuth.getAuthCode() //
console.log(code ) // { code: 'x/xxxxxxxxxx' }
// send the code to your auth server
// and retrieve a JWT or something to keep in localstorage
// to send on every request and compare with database
}
Server side
import { google } from 'googleapis'
const oauth2Client = new google.auth.OAuth2(GOOGLE_ID, GOOGLE_SECRET, 'postmessage')
google.options({ auth: oauth2Client })
async function getAccount(code) {
// the code you sent with the client
const { tokens } = await oauth2Client.getToken(code)
oauth2Client.setCredentials(tokens)
const oauth2 = google.oauth2({ version: 'v2' })
const { data: { id } } = await oauth2.userinfo.get()
// there you have the id of the user to store it in the database
// and send it back in a JWT
}

Resources