In this lesson you will use Auth0, a commercial implementation of the OAuth standard, to secure your Messages API using the programming language of your choice.
Auth0 is used by many well known companies to secure their Web APIs. Auth0 can be used both for authentication of users and authorization to access specific APIs. In this lesson we are going to focus on API authorization via tokens issued by the Auth0 platform.
The diagram below illustrates the OAuth flow we will use to secure our API resource. It consists of a client app requesting a token from Auth0, then passing this token as authorization to access the Messages API:
We firstly need to create an account with Auth0 and configure a new logical API and Application.
Go to https://auth0.com/signup - enter your email and then create a PERSONAL account type in the appropriate domain (EU or US)
Navigate to your Dashboard and select Applications->APIs->Create API
. Name your new API MessagesAPI
and set the Identifier to be messagesAPI
. Note that we are not actually creating a real API here, we are merely assigning a logical name for the API which Auth0 can use to secure access.
Click on your new API and navigate to the Test
tab. Click on Create & Authorize Test Application
. Again, we are not creating a real App here, we are merely assigning a logical name for an Application which will be authorised to access the Messages API.
You will then be presented with example code (in multiple languages) for your test application to request tokens from Auth0 and to send tokens to the MessagesAPI.
Use the example cURL request to help you construct a Postman request to obtain a new OAuth token.
If all goes well, you should receive a 200 OK and the body of the response should contain a JWT access token. Paste this into https://jwt.io and explore the contents.
Now we have the ability to request and send tokens, we need to integrate Auth0 with the code for our Messages API created in a previous lesson. Our Messages API was previously secured using Basic Auth, you now need to change the security mechanism to be OAuth.
Secure your API using OAuth - this will require you to research how to use OAuth within your language specific framework. The Additional Resources section of this page details how to set this up for Java (Spring Boot) and JavaScript (Express) developers - for other languages please see https://auth0.com/docs/quickstart/backend/ (for PHP see this link). Note that your Auth0 Domain, Client ID and Client Secret are considered sensitive data therefore you should ensure you use environment variables / configuration files to hold this information and add the file(s) to .gitignore
to avoid the data being saved to git.
Use Postman to send a token to an API endpoint. The token must be sent in an HTTP Authorization
header of type Bearer Token
, for example:
Authorization: "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjV1SlZpY09mY1FTQ19FRTF2VER2ZyJ9.eyJpc3MiOiJodHRwczovL2Rldi0xdTF5ZzYtbS5ldS5hdXRoMC5jb20vIiwic3ViIjoid1V1cGpaTVZiVlA5VlNaeGp0dElkTnVDeDd4TXhJR1hAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbWFuZHlzYXBpIiwiaWF0IjoxNjI5ODk4NTgwLCJleHAiOjE2Mjk5ODQ5ODAsImF6cCI6IndVdXBqWk1WYlZQOVZTWnhqdHRJZE51Q3g3eE14SUdYIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.FJyDPCgTUiwWw2707A56qwtlsUTJ-qqZDdY0CTW7m7kTqigJ0bayRuAa1KzJDB5HhBdDdLnVv1nzcWrOKvPd-84_51pnax8EOVkkW_vO4RL_zp89tENC9dHtXGMUqXhIwWMtiIjHoF1xJUmLfrlqmR9n3KtZr8cdELlTO7a1uESwCSQtd-iCtJ3mX2hkJ-zudmzHRxhTHGnR5XSQSx50JzKG24WXF7z-wAFuCgTadlyVvngytU0_MjyuS937kxqfDWVROy-xbo696k0iGcaH_fZo5YGkCPhKucx04FeZef52PwBelJZuN9gi6GGvj9JsZSb476Wf9Le2Y8qKwOeg2Q"
Verify you are not able to access any /messages
routes without a token. You should receive a status code of 401 Unauthorized.
Commit your code to GitHub and notify your coach that this assignment is complete.
GET /messages
and GET /messages/{id?}
endpoints and the other scope should allow access to all endpoints. Java developers may find this help for managing scopes for Java developers article useful.To support OAuth, you need to install some specific dependencies:
Dependency | Purpose |
---|---|
express-jwt | Provides Express middleware for validating JWTs |
jwks-rsa | Used to retrieve the public key used for signing the JWT |
cors | Used to support Cross-origin resource sharing (CORS) - a mechanism that allows resources to be requested from another domain outside the domain from which the first resource was served |
dotenv | Used to support the use of environment variables |
You will be storing details of your Auth0 account in an .env
file hence you must add .env
to your .gitignore
to avoid the sensitive data being saved to git.
Install the following node package dependencies:
npm install cors dotenv express-jwt jwks-rsa
Remove any dependency to express-basic-auth
Add the following to the start of your app.js
file as follows
const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const cors = require('cors');
require('dotenv').config('.env'); // Note: env vars should not be used in production
Add the following line AFTER the call to initialise Express
app.use(cors());
Create a .env
file to hold environment variables and add the following entries (substituting in your personal Auth0 domain):
AUTH0_AUDIENCE=messagesAPI
AUTH0_DOMAIN=[your domain].eu.auth0.com
Add a function to check for a valid OAuth (JWT) token:
// create middleware for checking the JWT
const checkJwt = jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${process.env.AUTH0_DOMAIN}.well-known/jwks.json`
}),
audience: process.env.AUTH0_AUDIENCE,
issuer: `https://${process.env.AUTH0_DOMAIN}`,
algorithms: ['RS256']
});
Secure your API:
app.get("/messages", checkJwt, (req, res) => {
You will make use of Spring security libraries which support OAuth.
Add the dependencies to your pom.xml
file:
<properties>
...
<spring-security.version>5.4.2</spring-security.version>
...
</properties>
<dependencies>
...
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
</dependency>
Create a new class which checks that the JWT has the correct audience value
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
/**
* Validates that the JWT is intended for our API.
*/
public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
private final String audience;
AudienceValidator(String audience) {
this.audience = audience;
}
public OAuth2TokenValidatorResult validate(Jwt jwt) {
OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);
if (jwt.getAudience().contains(audience)) {
return OAuth2TokenValidatorResult.success();
}
return OAuth2TokenValidatorResult.failure(error);
}
}
Modify your SecurityConfiguration to use OAuth
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Value("${auth0.audience}")
private String audience;
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.anyRequest()
.authenticated()
// ** IMPORTANT! do not use the line below in production apps!! **
.and().csrf().disable()
.cors()
.and().oauth2ResourceServer().jwt();
}
/**
* Required to enable CORS - NOT suitable for production code!
*
* @return CorsConfigurationSource cors configuration
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
// ** IMPORTANT! do not use the line below in production apps!! **
// ** Specify specific origins instead
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
}
Add a new file application.yml
under src/main/resources
to specify your Auth0 domain and audience
auth0:
audience: messagesAPI
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://[your Auth0 domain].eu.auth0.com/