auth0 / express-openid-connect

An Express.js middleware to protect OpenID Connect web applications.
MIT License
476 stars 141 forks source link

Microservices #165

Closed Cyben closed 3 years ago

Cyben commented 3 years ago

Hey, I wonder how to use this lib with a microservices architecture.

In my previous issue you added the link below(by the way thank you for the answer there and sorry for my inactivity) So about the link I saw that the node server renders the html pages but what should I do if I just use the node server for RESP api? (I would like to use the Authentication Code flow so the 'login' part would be from the frontend and the 'code to token' exchange would be from the backend)

https://github.com/auth0-samples/auth0-express-webapp-sample/tree/master/01-Login

Thank you

adamjmcgrath commented 3 years ago

Hey @Cyben

For protecting your microservice REST apis, you should use JWT authentication (see our quickstart on protecting an APIs https://auth0.com/docs/quickstart/backend/nodejs/01-authorization)

As you pointed out, this SDK is for protecting UI pages of a web application with a session.

So your UI Web App (either a SPA (see auth0-spa-js) or a webapp (this SDK)) logs in and get's an Access Token for a REST API (one of your microservices) that is protected with Access Token (a JWT) authorization

Cyben commented 3 years ago

Ohhh, thanks. So what is your recommendation and what is the right way to develop for example a frontend with react or any other framework and backend with node js for rest api for example by using the Authorization code flow

davidpatrick commented 3 years ago

Hey @Cyben if you are using React you can utilize our auth0-react SDK and follow the example on creating a useApi hook https://github.com/auth0/auth0-react/blob/master/EXAMPLES.md#4-create-a-useapi-hook-for-accessing-protected-apis-with-an-access-token

For protecting API routes we have a quickstart that you can follow https://auth0.com/docs/quickstart/backend/nodejs that uses the node-jwks-rsa library. We are working on some documentation for using this library in conjunction https://github.com/auth0/express-oauth2-bearer which will be a more modern library for handling protecting API routes on an Express app.

Cyben commented 3 years ago

Looking forward for the documentation, About your answer I took a quick look at the auth0-react SDK, and I guess this is a 'public client' right? My problem is that I have to work with a 'confidential client' and with the 'Authorization code flow' in the way that the redirect (for getting the code) is made from the frontend of course but then the code to token exchange is made from the backend so the client secret won't be shown in the browser.

If there is a forum for questions like that I would love to here. And thank you for all your help

davidpatrick commented 3 years ago

Hey @Cyben I apologize, yes the react is for public client. If you are working with the confidential client then you this is the correct library, in conjunction with https://auth0.com/docs/quickstart/backend/nodejs.

For the api endpoints you will want to protect those using https://github.com/auth0/node-jwks-rsa

Here is an example, of how that "might" look

const { auth, requiresAuth } = require('express-openid-connect');
const express = require('express');
const app = express();
const jwt = require('express-jwt');
const jwtAuthz = require('express-jwt-authz');
const jwksRsa = require('jwks-rsa');

const checkJwt = jwt({
  secret: jwksRsa.expressJwtSecret({ jwksUri: `${env.ISSUER_BASE_URL}/.well-known/jwks.json` }),
  audience: 'YOUR_API_IDENTIFIER',
  issuer: env.ISSUER_BASE_URL,
  algorithms: ['RS256']
});

app.use( auth({ authRequired: false }));

// Anyone can access this route
app.get('/', (req, res) => res.send('<a href="/admin">Admin Section</a>'));

// requiresAuth checks authentication.
app.get('/admin', requiresAuth(), (req, res) =>
  res.send(`Hello ${req.oidc.user.sub}, this is the admin section.`)
);

// this is the api section
app.get('/api/private', checkJwt, (req, res) =>
  res.json({
    message: 'Hello from a private endpoint! You need to be authenticated to see this.'
  })
);
Cyben commented 3 years ago

Hey, thank you very much for your help. Actually what you sent me is not what I was searching for, but it gave me an idea how to implement what I wanted.

I wanted a confidential client as a frontent and backend. The same client for both microservices. I wanted to use the Authorization code flow so the the authorization request (to get the 'code') will be from the frontend and then in the backend there will be the exchange for the token and some protected api's.

My backend code looks like this:

const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const app = express();

app.use(cors());
app.use(bodyParser.json()); // support json encoded bodies
app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies

const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const { Issuer } = require('openid-client');

let client;

Issuer.discover(env.ISSUER_BASE_URL) // => Promise
  .then(function (Issuer) {
    console.log('Discovered issuer %s %O', Issuer.issuer, Issuer.metadata);
    client = new Issuer.Client({
        client_id: env.CLIENT_ID,
        client_secret: env.CLIENT_SECRET
        redirect_uris: ['http://frontend/oauth-callback'], //frontend callback route
        response_types: ['code'],
      }); // => Client
  });

const checkJwt = jwt({
  secret: jwksRsa.expressJwtSecret({ jwksUri: `${env.ISSUER_BASE_URL}/protocol/openid-connect/certs` }),
  audience: 'AUD',
  issuer: env.ISSUER_BASE_URL,
  algorithms: ['RS256']
});

// I call this route from the frontend to get the authorizaionUrl and then making a redirect from the frontend
app.get('/login', (req, res) => {
    let authUrl = client.authorizationUrl({
        scope: 'openid'
    })
    res.send(authUrl)
})

// I call this route from the frontend to exchange the 'code' with the tokenSet
app.post('/code-to-token-exchange', (req, res) => {
    client.callback('http://frontend/oauth-callback', {code: req.body.code})
    .then((tokenSet) =>{
        console.log(tokenSet)
        console.log(tokenSet.claims())    
        res.send(tokenSet["access_token"])
    })
})

// API SECTION

// Anyone can access this route
app.get('/public', (req, res) => 
  res.json({
    message: 'Hello from a public endpoint! No need to be authenticated to see this.'
  })
);

// This is private and just by adding the access token to the request header it works, otherwise returns 401
app.get('/api/private', checkJwt, (req, res) =>
  res.json({
    message: 'Hello from a private endpoint! You need to be authenticated to see this.'
  })
);

I would love to hear from you what do you think about doing something like that? And it will be my pleasure to hear some tips and fixes I should make or add.

Cyben commented 3 years ago

Maybe is there a way of using this lib but instead of making the redirect automatically (when using the Authorization code flow) it will return the authorizationUrl?

adamjmcgrath commented 3 years ago

Hey @Cyben - if it's all the same app, you could just use this library and protect /api/private with the session cookie, eg

const { auth, requiresAuth } = require('express-openid-connect');

app.use(
  auth({
    authRequired: false,
  })
);

// Anyone can access this route
app.get('/public', (req, res) => 
  res.json({
    message: 'Hello from a public endpoint! No need to be authenticated to see this.'
  })
);

// This is private, you can only access it with a valid session cookie
app.get('/api/private', requiresAuth(), (req, res) =>
  res.json({
    message: 'Hello from a private endpoint! You need to be authenticated to see this.'
  })
);
Cyben commented 3 years ago

I have an app, a frontend in react and a backend in node js. I run them independently. But they are both the same confidential client (same client id). I'm using the Authorization code flow, that means that the initial request should be from the frontend to be able to redirect and the exchange of the code to the token should be from the backend because there is a use of the client secret. (This is the best practice)

adamjmcgrath commented 3 years ago

I'm using the Authorization code flow, that means that the initial request should be from the frontend to be able to redirect and the exchange of the code to the token should be from the backend because there is a use of the client secret. (This is the best practice)

Sure, if you're happy with this that's fine. You can do the whole Auth code flow (with code exchange) on the front end public client (see https://auth0.com/docs/flows/authorization-code-flow-with-proof-key-for-code-exchange-pkce), then you don't need the login and /code-to-token-exchange- since you have an unprotected route doing the code exchange and you're sending the AT to the frontend, I don't see the difference wrt best practice.

Either way, I think this is moved beyond the scope of this library, I recommend you continue the conversation in the Auth0 Community forum to get feedback on general app architectures

Cyben commented 3 years ago

Thank you very much.