Can an open id connect id token be used to authenticate to an api - node.js

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.

Related

How to validate Google access token locally using google oAuth libraries

I'm trying to use Google's APIs to modify data on my users' Google account through the use of an id_token for authentication and an access_token to actually use Google's APIs. I know I'm able to verify the authenticity of an id token like such:
import { OAuth2Client } from "google-auth-library";
const client = new OAuth2Client(GOOGLE_CLIENT_ID);
const ticket = await client.verifyIdToken({
token: idToken,
audience: GOOGLE_CLIENT_ID,
});
This verification happens locally on my device without needing to contact Google's servers each time a token needs to be verified.
I tried to figure out how to do the same for the access_token. The top answer on How can I verify a Google authentication API access token? post suggests that I should call an endpoint https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=accessToken to do the verification but that defeats my purpose of trying to do it locally.
What Google OAuth library/method can I use to verify an access token locally. Is it even possible?
Just to reiterate, I'm talking about the access_token, not the id_token.

Azure App Service Easy Auth

I have an Azure mobile backend set up with easy auth for facebook and google authentication and it works as expected.
Every time a user signs in with any of the supported providers, I want to be able to verify if it's a new user or not (e-mail not in database), without make an additional call from client. Is this possible?
Every time a user signs in with any of the supported providers, I want to be able to verify if it's a new user or not (e-mail not in database), without make an additional call from client. Is this possible?
As far as I know, we couldn't directly verify if it's a new user or not.
No matter you use server flow or client flow, easy auth will just return access token for the client to access the mobile backend resources, it will not check the user is new or old.
If you want to achieve this requirement, you need write your own logic.
You could write codes after the user login successfully.
For example, facebook login.
If you the use have login successfully,you could call GetAppServiceIdentityAsync extension method to get the login credentials, which include the access token needed to make requests against the Facebook Graph API.
// Get the credentials for the logged-in user.
var credentials =
await this.User
.GetAppServiceIdentityAsync<FacebookCredentials>(this.Request);
if (credentials.Provider == "Facebook")
{
// Create a query string with the Facebook access token.
var fbRequestUrl = "https://graph.facebook.com/me/feed?access_token="
+ credentials.AccessToken;
// Create an HttpClient request.
var client = new System.Net.Http.HttpClient();
// Request the current user info from Facebook.
var resp = await client.GetAsync(fbRequestUrl);
resp.EnsureSuccessStatusCode();
// Do something here with the Facebook user information.
var fbInfo = await resp.Content.ReadAsStringAsync();
}
Then you could check the database according to the user information.
More details about how to get user information in server side, you could refer to How to: Retrieve authenticated user information.

Firebase REST auth when creating token with node.js admin sdk

I know this issue was asked a lot here, but I still cannot seem to find the exact answer that can solve my problem.
I wish to access Firebase using REST calls, by adding an access_token param.
The access_token is created using the Node.js Admin SDK, using the following code:
var admin = require("firebase-admin");
var serviceAccount = require("./pk.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://XXX.firebaseio.com"
});
var uid = "1234";
admin.auth().createCustomToken(uid)
.then(function(customToken) {
// Send token back to client
console.log("Token: "+customToken);
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
The problem is that if I take the token created from Node.js and use it my REST call, I get an Unauthorized request error.
I have read in some questions that people added the scope param when issuing the token, but have not found a way to do that with Node.js Admin SDK.
Google's docs are not so detailed with this issue. Any idea what I might try to resolve this one?
The token you are using to authenticate to the Firebase REST API is not the correct type of token. You are using a Firebase Auth custom token, which can only be used to authenticate one of the Firebase client SDKs via the signInWithCustomToken() method as explained in Sign in using custom tokens on clients.
In order to authenticate to the Firebase REST API, you have two options: Firebase ID tokens (for user-based access) or Google OAuth2 access tokens (for admin access).
Authenticate with Firebase ID Tokens
See Retrieve ID tokens on the client for an explanation of how to retrieve access tokens in the various Firebase client SDKs. You can also exchange a Firebase custom token for an ID token and refresh token pair via an undocumented REST API:
Endpoint: https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=<API_KEY>
Method: POST
Request Body: { "token": <CUSTOM_TOKEN>, "returnSecureToken": true }
<API_KEY> is the same API key you get from your Firebase Console that you use in the Firebase clients. <CUSTOM_TOKEN> is a Firebase custom token.
Since ID tokens expire after an hour, you will need to use the refresh token to refresh them via this other undocumented REST API:
Endpoint: https://securetoken.googleapis.com/v1/token?key=<API_KEY>
Method: POST
Request Body: { "refresh_token": <REFRESH_TOKEN>, "grant_type": "refresh_token" }
<API_KEY> is the same API key as before. <REFRESH_TOKEN> is the refresh token from the previous API call.
Once you have an ID token, you can pass that to the REST API via the auth query parameter to authenticate a request. The request respects Firebase Security Rules as if the end user logged into the client was making the request.
Authenticate with Google Access Tokens
To authenticate with a Google OAuth2 access token, the first thing you need to do is get one. See Retrieving an access token for an explanation of how to do this. It only currently includes a Java example, but this is possible in many languages, including Node.js. Once you have an ID token, you can pass that to the REST API via the access_token query parameter to authenticate a request. The request will be made with admin access, overriding all Firebase Security Rules and granting full read and write access.

Auth0 authentication of single-page-app on a different domain than the api

I'm trying add Auth0 authentication to my single-page-app. My app is running under a domain, say app.mycompany.com, whereas the api used by this app is running under a different domain, say api.mycompany.com.
I'm aware of this thread:
Single Sign On (SSO) solution/architecture for Single Page App (SPA)
and the auth0 articles and github repositories linked by here. But I have a feeling that my scenario is slightly simpler, as I don't necessarily want to have single-sign-on between several different single-page-apps. For a start I just want the seperation between the API and the app.
Here is what I have tried already:
I already started from the article React Login With Auth0 and downloaded the starter project. I can surely login without problems and it will leave me with an id_token in my localStorage containing a JWS issued by Auth0.
I can also login directly on api.mycompany.com (my FeathersJS API application) and I can see that during the OAuth redirecting process, the id_token token is magically translated to a feathers-jwt token issued by my Feathers application containing the internal ID of the user-object matching the auth0-ID. I also have implemented the logic used to map from the Auth0-ID to my internal ID. Furthermore all my Feathers hooks such as validation of token and population of the user are working.
What I cannot figure out is how to alter the react-application running under app.mycompany.com with an Auth0-token in localStorage, so that this token is translated to a feathers-jwt token by api.mycompany.com, in such a way that all succeeding API-calls automatically has the feathers-jwt token included so the API can validate the user and return the right data.
Any suggestions on how to proceed will be greatly appreciated.
A couple of more background details:
The api is built on node.js and featherjs (which basically is an extension of Express)
The single-page-app is built on ReactJS and is served by a simple Express server, but it could be served by any server that can serve static files over http. The single-page-app makes http-requests to the api to read data and perform operations.
The api has the following lines of code taking care of the authentication:
const authentication = require('feathers-authentication');
const Auth0Strategy = require('passport-auth0').Strategy;
app.configure(authentication({
local:false,
token: {
secret: 'mysecret',
payload: ['email', 'auth0Nickname'],
issuer: 'mycompany'
},
idField: 'id',
shouldSetupSuccessRoute: false,
auth0: {
strategy: Auth0Strategy,
domain: 'mycompany.eu.auth0.com',
'clientID': 'xxx',
'clientSecret': 'yyy'
}
}));
I had exactly the same problem as you, I wanted to authenticate a user from a single page application, calling the API located on an other server.
The official auth0 example is a classic Express web application that does authentication and renders html page, but it's not a SPA connected to an API hosted on an other domain.
Let's break up what happens when the user authenticates in this example:
The user makes a request calling /auth/auth0 route
The user is automatically redirected to the Auth0 authentication process (Auth0 login form to choose the provider and then the provider login screen)
The user is redirected to /auth/success route
/auth/success route redirects to the static html page public/success.html, also sending a jwt-token cookie that contains the user's token
Client-side, when public/success.html loads, Feathers client authenticate() method reads the token from the cookie and saves it in the local storage.
From now, the Feathers client will authenticate the user reading the cookie from the local storage.
I tried to adapt this scenario to a single-page application architecture, implementing the following process:
From the SPA, call the authentication API with a source query string parameter that contains the SPA URL. For example: http://my-api.com/auth/auth0?source=http://my-spa.com
Server-side, in /auth/auth0 route handler, create a cookie to store that URL
After a successful login, read the source cookie to redirect the user back to the SPA, sending the JWT token in a cookie.
But the last step didn't work because you can't set a cookie on a given domain (the API server domain) and redirect the user to an other domain! (more on this here on Stackoverflow)
So actually I solved the problem by:
server-side: sending the token back to the client using the URL hash.
client-side: create a new html page that reads the token from the URL hash
Server-side code:
// Add a middleware to write in a cookie where the user comes from
// This cookie will be used later to redirect the user to the SPA
app.get('/auth/auth0', (req, res, next) => {
const { origin } = req.query
if (origin) {
res.cookie(WEB_CLIENT_COOKIE, origin)
} else {
res.clearCookie(WEB_CLIENT_COOKIE)
}
next()
})
// Route called after a successful login
// Redirect the user to the single-page application "forwarding" the auth token
app.get('/auth/success', (req, res) => {
const origin = req.cookies[WEB_CLIENT_COOKIE]
if (origin) {
// if there is a cookie that contains the URL source, redirect the user to this URL
// and send the user's token in the URL hash
const token = req.cookies['feathers-jwt']
const redirectUrl = `${origin}/auth0.html#${token}`
res.redirect(redirectUrl)
} else {
// otherwise send the static page on the same domain.
res.sendFile(path.resolve(process.cwd(), 'public', 'success.html'))
}
})
Client-side, auth0.html page in the SPA
In the SPA, I created a new html page I called auth0.html that does 3 things:
it reads the token from the hash
it saves it in the local storage (to mimic what the Feathers client does)
it redirects the user to the SPA main page index.html
html code:
<html>
<body>
<script>
function init() {
const token = getToken()
if (!token) {
console.error('No auth token found in the URL hash!')
}
// Save the token in the local storage
window.localStorage.setItem('feathers-jwt', token)
// Redirect to the single-page application
window.location.href = '/'
}
// Read the token from the URL hash
function getToken() {
const hash = self.location.hash
const array = /#(.*)/.exec(hash)
if (!array) return
return array[1]
}
init()
</script>
</body>
</html>
And now in the SPA I can use the Feathers client, reading the token from the local storage when the app starts.
Let me know if it makes sense, thank you!
If you haven't done so, you should follow this article (React Login with Auth0) to implement the authentication on your React application. If you already tried to follow it, update your question with specific issues you faced.
Even though you currently not need SSO, the actual implementation of the authentication in your application will not vary much. By using Auth0 enabling SSO across your apps is mostly enabling configuration switches.
Finally for a full reference with all the theory behind the security related aspects of your exact scenario check:
Auth0 Architecture Scenarios: SPA + API
Update:
The full scenario I linked too covers the most comprehensive scenarios where an API is accessed by a multitude of client applications that may even be developed by third-parties that do not own the protected API, but want to access the data behind it.
It does this by leveraging recent features that are currently only available in the US region and that at a very high level can be described as an OAuth 2.0 authorization server delivered as a service.
Your particular scenario is simpler, both the API and client application are under control of the same entity, so you have another option.
Option 1 - Leverage the API authorization through Auth0 US region only (for now)
In this situation your client application, at authentication time, would receive an id_token that would be used to know the currently authenticated user and would also receive an access_token that could be used to call the API on behalf of the authenticated user.
This makes a clear separation between the client application and the API; the id_token is for client application usage and the access_token for API usage.
It has the benefit that authorization is clearly separated from authentication and you can have a very fine-grained control over authorization decisions by controlling the scopes included in the access token.
Option 2 - Authenticate in client application and API in the same way
You can deploy your client application and API separately, but still treat them from a conceptual perspective as the same application (you would have one client configured in Auth0 representing both client-side and API).
This has the benefit that you could use the id_token that is obtained after authentication completes to know who the user was on the client-side and also as the mechanism to authenticate each API request.
You would have to configure feathers API to validate the Auth0 id_token as an accepted token for accessing the API. This means that you don't use any feathers based on authentication on the API, that is, you just accept tokens issued by Auth0 to your application as the way to validate the access.

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