asp.net-corejwtauthorizationauth0

How to Implement Token Validation for External Users Consuming My Public API Using Auth0?


I have a public-facing API and need to authorize external users (from outside my system) who are trying to consume this API. These users are already logged into their respective systems and possess access tokens issued by their own identity providers (IDPs). My goal is to validate these tokens before granting access to my API and process the request only if the user has the required claims. Unauthorized users should be denied access.

To implement this, I plan to use Auth0 as the authorization mechanism on my side. I also need to handle the following aspects effectively:

  1. Access Token Validation:
  1. Claims Validation:

3.Communication Between Systems:

4.Auth0 Integration:

5.Security Considerations:


Solution

  • By default, Auth0 issues something known as opaque token instead of a JSON Web Token(JWT). It happens when the token is intended only for Auth0's own API. To get a JWT access token from Auth0, which you can verify at your own backend server:

    Auth0 Configuration

    1. Define a Custom API in Auth0: Go to your Auth0 dashboard, navigate to APIs, and create a new API. Set the name of your choice. Set the Identifier, it should be the address of your backend server, (e.g. If the server is running on local machine at port 8080, then the Identifier should be: http://localhost:8080). Set the JSON Web Token Signing Algorithm as "RS256". (RS256 offers better security than HS256)

    This Identifier servers as the API Audience which will be used to request a JSON Web Token from Auth0.

    Frontend Configuration

    1. Update the Auth0 Provider in your React App. (Assuming you are using React)
        import { Auth0Provider } from "@auth0/auth0-react";
    
        <Auth0Provider
          domain="YOUR_AUTH0_DOMAIN" // The domain of your Auth0 Application
          clientId="YOUR_AUTH0_CLIENT_ID" // The client id of your Auth0 Application
          authorizationParams={{
            redirect_uri: window.location.origin,
            audience: "AUTH0_IDENTIFIER", // The identifier from your new API
          }}
        >
          {/* Rest of your app */}
        </Auth0Provider>;
    
    1. Request the Token with the Audience: You can use Auth0's library's function getAccessTokenSilently() to get the token.
        const accessToken = await getAccessTokenSilently({
          audience: "AUTH0_AUDIENCE", // The same audience i.e The Identifier from your new API
        });
    
    1. Now you can send this accessToken to your backend server for authentication/authorization purposes in the request header.
        const sendRequest = async () => {
            try {
              const response = await axios.get(
                "YOUR_API",
                {
                  headers: {
                    Authorization: `Bearer ${accessToken}`,
                  },
                }
              );
            } catch (err) {
              console.error(err);
            }
        };    
    

    Backend Configuration

    1. Now at the backend, in order to validate the token, you need the signing key from Auth0. Install dependencies:

    jsonwebtoken

    jwks-rsa

    const jwt = require("jsonwebtoken");
    const jwksClient = require("jwks-rsa");
    
    const client = jwksClient({
      jwksUri: "https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json", // The domain of your Auth0 Application
    });
    
    function getKey(header, callback) {
      // This function will fetch the signing key for your accessTokens
      client.getSigningKey(header.kid, (err, key) => {
        const signingKey = key.getPublicKey();
        callback(null, signingKey);
      });
    }
    
    const validateToken = (req, res, next) => {
      // This function will validate the accessToken received in the request header
      const authHeader = req.headers.authorization;
      if (!authHeader || !authHeader.startsWith("Bearer ")) {
        return res.status(401).json({ error: "No token provided" });
      }
    
      const token = authHeader.split(" ")[1];
      jwt.verify(
        token,
        getKey,
        {
          audience: "YOUR_API_IDENTIFIER", // Identifier from your new API
          issuer: "https://YOUR_AUTH0_DOMAIN/", // The domain of your Auth0 Application
          algorithms: ["RS256"],
        },
        (err, decoded) => {
          if (err) {
            console.log(err);
            return res.status(401).json({ error: "Token verification failed" });
          } else {
            console.log("Token verification successful");
            req.user = decoded; // Pass decoded token to next middleware
            next();
          }
        }
      );
    };
    
    module.exports = validateToken;
    
    

    The decoded token will contain the details of the user. However it will not contain the Roles of the user.


    For claims validation, you can assign "Roles" to users in Auth0. To verify roles of each user at your backend server, you will need a Management API for your Auth0 Application. Using that Management API you can make calls from your backend server to Auth0 to verify user's Roles.

    Refer to this documentation for further details