How to consume node-oidc-provider access_token with express? - node.js

I believe it's silly question but how to use node-oidc-provider with express? So I got access_token on the client side, sent request with Bearer {access_token} and what's next? How can I obtain user from that token? I believe oidc-provider must have some middleware or anything which can be used for that but I didn't find any documentation on that topic. The only thing I found is how to check if user is logged in:
const ctx = provider.app.createContext(req, res)
const session = await provider.Session.get(ctx)
const signedIn = !!session.account
But it doesn't work for me and it looks like it's using cookies inside by some reason so session is null in that case.

You would make a request to the user_info endpoint using the access token in the authorization header. The url would be should in the .well-known/openid-configuration endpoint of whatever route you attached the provider to.

Related

Delegate authentication token from react app to express app with OpenID and passportjs

I am using the OpenID client to authenticate the user, what I would like to do is call a protected route on an express API.
I am trying to share the authorization cookie obtained with the react app with OpenID, and the xenter image description here
I tried:
simply passport.authenticate(), with the openID strategy, but does redirection which isnt necessary
Not sure if this is the correct way but, this is how I kinda did it:
const user = await auth0Client.getUser();
// Attach token
const accessToken = await auth0Client.getTokenSilently();
axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
Then I just used the passport JWT strategy combined with jwks-rsa library to obtain the secret.

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()

node js JWT get current user

I work on app with an authentication using Node JS, JWT and Sequelize for the API and I'm using React JS / redux for the front part. I'm successfully implemented the login/logout/register parts for the application, but now I need to access to the current_user logged in.
I put a JWT in the localStorage, but I want to have access to the user ID, user email, user name and more informations about my user currently logged in.
Should I use Cookies ? LocalStorage ? Or should I create a currentUser method in my API ?
I'm a bit lost with this, if someone could help me find some usefull resources or advices !
Thank !
If you put that information in the payload of the JWT, then you can get it without decoding on the server or needing the secret, and can therefore put the token in LocalStorage for use whenever. By the spec, a JWT is <headerINfo>.<payloadInfo>.<signature>. So on the client, you can just do:
// given a payload object of { username: 'bob', userid: 1, email: 'bob#example.com' }
const tokenParts = token.split('.');
const encodedPayload = tokenParts[1];
const rawPayload = atob(encodedPayload);
const user = JSON.parse(rawPayload);
console.log(user.username); // outputs 'bob'
Obviously, this info is available to any client that has access to the Token, so you only want to put stuff in the payload that that's OK for.
Storing the token in LocalStorage is fine. If you need to fetch the user details, create an endpoint in your API such as getUser. You can then use jwt.decode(accessToken, JWT SECRET HERE) and return the decoded value (which will be your user) assuming the accessToken is valid.
You can make a middleware if you haven't already that will ensure that user info is always available to those routes that require it:
const auth = jwt({
secret: JWT_SECRET,
userProperty: 'payload',
algorithms: ['HS256']
});
module.exports = auth;
Then you should have req.payload with user details. Alternatively you can check the Authorization property in your headers depending on how you set up your app.
When logging in, server should send token and user data, you can store that data in Redux store. Then simply request data from there. When user reloads page, send API request with JWT token and server should return user data, that you will again put in Redux store.

Passport JWT is always returning 401 unauthorized when using OpenID Connect ID Token

I am following this tutorial to enable jwt authentication in my express API.
https://jonathanmh.com/express-passport-json-web-token-jwt-authentication-beginners/
If I use a standard username/password authentication, I am able to use JwtStrategy to authenticate the JWT Token that I receive in the request header. jwt.sign() happens on the user id and secret. All of this works fine.
When I try to modify this code to verify the id_token (JWT Token signed using RS256) from OpenID Connect, then I get 401 Unauthorized no matter what. I tried to debug in the JwtStrategy method and it looks like the request doesn't even go inside that function. This id_token appears to be a lot longer than the one signed with HS256 algorithm.
A simple passport.authenticate call app.get('/callback', passport.authenticate('jwt', { session: false }), function(req, res, next) {
});
Can someone please explain why it doesn't even recognise my token?
It depends on how you are passing the token from client and reading it in server. The request will go into the validate function only if it receives the token correctly. Check similar post https://stackoverflow.com/a/46020083/4548946.
It worked for me. Hope it helps you too.

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