This quickstart / sample shows how can you secure a Node (Express) API using IdentityServer4 as the security token service. Specifically, it uses the JWKS endpoint and RS256 algorithm.
We will use 2 modules from Auth0 to deal with JWTs & JWKS:
For further background on using RS256 & JWKS in Node apps, consider this excellent article which explains basically how the jwks-rsa module works.
IdentityServer provides a JWKS endpoint at the URI specified with the jwks_uri
key in the discovery document available from /.well-known/openid-configuration
. By default, this is located at /.well-known/openid-configuration/jwks
so in this sample, we can access it at http://localhost:5000/.well-known/openid-configuration/jwks.
This repo contains 4 sample projects:
-
idserv4 - IdentityServer4 setup with user authentication as per the Quickstarts #3 from the official docs. We also setup a self-signed certificate (cert.pfx) for credential signing that will be explained below. When running, this will be accessible at http://localhost:5000
-
console - Sample .NET Core 2.1 Console app as per the "Creating the client" section from the docs. This app demonstrates using the ClientCredentials grant type to obtain the access token from IdentityServer and use it to call the sample Node API at http://localhost:5002/me
-
node-api - Node Express based sample API which is the whole point of this repo! This is hosted at http://localhost:5002 by default. There's only 1 Javascript file (index.js) that shows the implementation, but it's documented with useful comments and should be fairly easy to follow.
-
client - Javascript OIDC client based on the JsOidc sample. This uses webpack-dev-server to host the client app. The "Call API" button is configured to access the Node API at http://localhost:5002/me
Two test users are configured with username/password:
bob/bob
andalice/alice
that you can use to login.
In order for IdentityServer to support signing JWTs with RS256, we need to configure it to use a signing certificate. This is basically done by supplying an X509Certificate2 to the AddSigningCredential
extension method in Startup:
// File: idserv4/Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddSigningCredential(Config.GetSigningCertificate(_env.ContentRootPath))
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(TestUsers.Users);
}
// File: idserv4/Config.cs
public class Config
{
internal static X509Certificate2 GetSigningCertificate(string rootPath)
{
var fileName = Path.Combine(rootPath, "cert.pfx");
if(!File.Exists(fileName)) {
throw new FileNotFoundException("Signing Certificate is missing!");
}
var cert = new X509Certificate2(fileName);
return cert;
}
...
Included in the sample is a self-signed PFX (cert.pfx
) generated using OpenSSL. You can generate your own self-signed certs using the following commands:
openssl req -x509 -days 365 -newkey rsa:4096 -keyout key.pem -out cert.pem
openssl pkcs12 -export -in cert.pem -inkey key.pem -out cert.pfx
As an aside, you can get OpenSSL for Windows from here. For supporting Node development on Windows, I recommend getting the older v1.0.2L instead due to this.
Instead of using OpenSSL, you can also use Powershell to generate self-signed certs.
The main gist of how to do this is defined with the auth
middleware in the sample Node app:
// File: node-api/index.js
// reference to IdentityServer instance
const issuer = 'http://localhost:5000'; // can potentially use the "iss" claim from the access token instead
// define authentication middleware
const auth = jwt({
secret: jwksClient.expressJwtSecret({
cache: true, // see https://github.com/auth0/node-jwks-rsa#caching
rateLimit: true, // see https://github.com/auth0/node-jwks-rsa#rate-limiting
jwksRequestsPerMinute: 2,
jwksUri: `${issuer}/.well-known/openid-configuration/jwks` // we are hardcoding the default location of the JWKS Uri here - but another approach is to get the value from the discovery endpoint
}),
// validate the audience & issuer from received token vs JWKS endpoint
audience: 'api1',
issuer: issuer,
algorithms: ['RS256']
});
...
Here we specify the IdentityServer4 instance (http://localhost:5000), and setup some options on the jwks-rsa
module. This module supports validating the scope in the token against a specific audience (api1
in this case).
- Clone this repo:
git clone https://github.com/lyphtec/idserv4-node-jwk cd idserv4-node-jwks
- Run docker-compose:
docker-compose up
- Access the client app at http://localhost:5005
-
Clone this repo:
git clone https://github.com/lyphtec/idserv4-node-jwks cd idserv4-node-jwks
-
Run IdentityServer:
cd src/idserv4 dotnet restore dotnet build dotnet run
Check that it's up & running at http://localhost:5000/.well-known/openid-configuration
Check that JWKS endpoint has been setup correctly with RS256 at http://localhost:5000/.well-known/openid-configuration/jwks. There should be
x5t
&x5c
properties for the JWK. x5c is the public key of our certificate. -
Open a new console & startup the Node API:
cd src/node-api yarn install # (or npm install) yarn start # (or npm start)
Check that it's up & running at http://localhost:5002. It should return "Hello"
If we try to access the "secured" API at http://localhost:5002/me using a browser, it should return a 401 as we haven't passed the access token into the authorization Bearer header.
-
Open another console & run the sample "console" app:
cd /src/console dotnet restore dotnet build dotnet run
The decoded payload from the access token should match the claims returned from the secured Node API endpoint.
-
Run the sample client Javascript app:
cd /src/client yarn install # (or npm install) yarn start # (or npm start)
App should be up & running at http://localhost:5005
Our Node API is now secured by IdentityServer!