Repository: gofr-dev/gofr

Issue:
Allow to Check expiration of JWT Token
Currently, Oauth middleware validates if the JWT token received is valid or not but it does not check if it is expired.

As per, [RFC-7519](https://www.rfc-editor.org/rfc/rfc7519#page-9) it is optional, but how would user configure the middleware to check? Right Now, user would need to check in handler or write their own middleware - which shouldn't be the case

There should be an option while user set the JWKS url to enable these feature to validate the token expiry.

Similar to expiry the follwing should also be checked:

1. Issuer
2. Audience
3. Not Before Claim


* JTI may be can be used to manage the user sessions from the middleware itself will create a separate ticket for this.


Discussion:
@RahulMohanK: @aryanmehrotra I am interested to work on this issue. Could you assign this to me ?@aryanmehrotra: Sure, assigning to you, before implementing can you please share the proposed solution and how user would be using it.@Umang01-hash: @RahulMohanK have you thought of any approach on how can we make it configurable to check if JWT token expiration check is needed? Please feel free to share it here.@RahulMohanK: @aryanmehrotra @Umang01-hash I have added the possible design change in oauth.go and gofr.go [here](https://github.com/user-attachments/files/17912889/gofr-jwt-validation.pdf). Please check the same.
@aryanmehrotra: @RahulMohanK changing the signature of EnableOauth introduces a breaking change, we need to figure out a different approach which does not break it for existing users.@RahulMohanK: @aryanmehrotra To minimize the impact of breaking change the next best option is to introduce a new [configuration ](https://gofr.dev/docs/quick-start/configuration )that users can use to explicitly validate the exp of the jwt.

Users would only need to set this config, gofr will validate exp if and only if the config is set by the end user.

Name of the configuration could be JWKS_VALIDATE_EXP which can be set to true or false. Absence of the config key implies that the value is false by default.

We would only need to introduce the change in Oauth.go parseToken function.
@aryanmehrotra: @RahulMohanK this seems to be a nice option but as user would anyway need to enable oauth using app.EnableOauth it would be better if we take variadic parameters such that it neither breaks nor allows to manage things from multiple places that is configs and the main.go - anyway if the user wished to use the configs to fetch the values they may read the values from the env file using app.Config.

Moreover, I am updating the ticket with more use cases of the same.

Should we accept an option for all these claims, or it would be better if take some map or something such that users can also provide their own claim if needed.@danysz: Just take in consideration that if you implement the expiration claim than it should be done including how much time is allowed after the expiration. 

```
   The "exp" (expiration time) claim identifies the expiration time on
   or after which the JWT MUST NOT be accepted for processing.  The
   processing of the "exp" claim requires that the current date/time
   MUST be before the expiration date/time listed in the "exp" claim.
   Implementers MAY provide for some small leeway, usually no more than
   a few minutes, to account for clock skew.  Its value MUST be a number
   containing a NumericDate value.  Use of this claim is OPTIONAL.@Umang01-hash: @RahulMohanK are you still working on this issue?
If yes can you please give an update regarding the status of it?@coolwednesday: @Umang01-hash , if this issue is stale, can you assign it to me ?
 @RahulMohanK: @Umang01-hash I am still working on this issue, will raise PR soon.@Umang01-hash: @RahulMohanK any update on the PR?@Umang01-hash: @RahulMohanK If you are unavailable to take this up please let us know. We will assign it to somebody else.@Umang01-hash: @coolwednesday assigning it to you.

Solution patch:
diff --git a/docs/advanced-guide/http-authentication/page.md b/docs/advanced-guide/http-authentication/page.md
index 4bbc57f9..2531d556 100644
--- a/docs/advanced-guide/http-authentication/page.md
+++ b/docs/advanced-guide/http-authentication/page.md
@@ -138,15 +138,72 @@ It involves sending the prefix `Bearer` trailed by the encoded token within the
 GoFr supports authenticating tokens encoded by algorithm `RS256/384/512`. 
 
 ### App level Authentication
-Enable OAuth 2.0 with three-legged flow to authenticate requests
+Enable OAuth 2.0 with three-legged flow to authenticate requests. Use `EnableOAuth(jwks-endpoint,refresh_interval, options ...jwt.ParserOption)` to configure GoFr with pre-defined credentials.
 
-Use `EnableOAuth(jwks-endpoint,refresh_interval)` to configure GoFr with pre-defined credentials.
+### Description
+`EnableOAuth` configures OAuth authentication middleware for the application.
+
+- It registers a new HTTP service to fetch **JSON Web Key Sets (JWKS)**, which are used to verify JWTs.
+- The JWKS endpoint is periodically refreshed based on the specified refresh interval.
+- Additional JWT validation options can be passed using `jwt.ParserOption`, allowing fine-grained control over claim validation.
+
+### Parameters
+| Parameter        | Type                | Description |
+|------------------|-------------------|-------------|
+| `jwksEndpoint`   | `string`           | URL of the JWKS endpoint used to retrieve signing keys for token verification. |
+| `refreshInterval` | `int`              | Interval (in seconds) at which the JWKS cache is refreshed. |
+| `options`        | `...jwt.ParserOption` | Optional JWT claim validation configurations, such as issuer, audience, and expiration requirements. |
+
+### Available JWT Claim Validations
+
+#### Expiration (`exp`) Validation
+If the `exp` claim is present, it is always validated to ensure the token has not expired. However, to make the `exp` claim mandatory in our JWT tokens, we can use:
+
+```go
+jwt.WithExpirationRequired()
+```
+> This ensures that every token must include the `exp` claim, making expiration validation a strict requirement.
+
+#### Issued At (`iat`) Validation
+If the `iat` claim is present, it is ensured that tokens are not accepted before their issuance time. No additional configuration is needed for this validation.
+
+#### Not Before (`nbf`) Validation
+If the `nbf` claim is present, it is always validated to ensure that a JWT is only valid after a certain time. No additional configuration is needed for this validation.
+
+#### Audience (`aud`) Validation
+Verifies that the token is intended for the expected audience.
+
+```go
+jwt.WithAudience("https://api.example.com")
+```
+
+#### Subject (`sub`) Validation
+Ensures the token is associated with the expected subject.
+
+```go
+jwt.WithSubject("user@example.com")
+```
+
+#### Issuer (`iss`) Validation
+Ensures the token is issued by a trusted authority.
+
+```go
+jwt.WithIssuer("https://auth.example.com")
+```
+
+### Example
 
 ```go
 func main() {
 	app := gofr.New()
 
-	app.EnableOAuth("http://jwks-endpoint", 20)
+	app.EnableOAuth(
+		"http://jwks-endpoint", 
+		20,
+        jwt.WithExpirationRequired(), // to enforce presence of exp claim in every token
+        jwt.WithAudience("https://api.example.com"),
+        jwt.WithIssuer("https://auth.example.com")
+		)
 
 	app.GET("/protected-resource", func(c *gofr.Context) (interface{}, error) {
 		// Handle protected resource access
diff --git a/pkg/gofr/auth.go b/pkg/gofr/auth.go
index 174fd0ee..4c104b4d 100644
--- a/pkg/gofr/auth.go
+++ b/pkg/gofr/auth.go
@@ -3,6 +3,8 @@ package gofr
 import (
 	"time"
 
+	"github.com/golang-jwt/jwt/v5"
+
 	"gofr.dev/pkg/gofr/container"
 	"gofr.dev/pkg/gofr/http/middleware"
 )
@@ -84,7 +86,13 @@ func (a *App) EnableAPIKeyAuthWithValidator(validateFunc func(c *container.Conta
 //
 // The JWKS endpoint is used to retrieve JSON Web Key Sets for verifying tokens.
 // The refresh interval specifies how often to refresh the token cache.
-func (a *App) EnableOAuth(jwksEndpoint string, refreshInterval int) {
+// We can define optional JWT claim validation settings, including issuer, audience, and expiration checks.
+// Accepts jwt.ParserOption for additional parsing options:
+// https://pkg.go.dev/github.com/golang-jwt/jwt/v4#ParserOption
+func (a *App) EnableOAuth(jwksEndpoint string,
+	refreshInterval int,
+	options ...jwt.ParserOption,
+) {
 	a.AddHTTPService("gofr_oauth", jwksEndpoint)
 
 	oauthOption := middleware.OauthConfigs{
@@ -92,5 +100,5 @@ func (a *App) EnableOAuth(jwksEndpoint string, refreshInterval int) {
 		RefreshInterval: time.Second * time.Duration(refreshInterval),
 	}
 
-	a.httpServer.router.Use(middleware.OAuth(middleware.NewOAuth(oauthOption)))
+	a.httpServer.router.Use(middleware.OAuth(middleware.NewOAuth(oauthOption), options...))
 }
diff --git a/pkg/gofr/http/middleware/oauth.go b/pkg/gofr/http/middleware/oauth.go
index fc6917ed..6a4850c5 100644
--- a/pkg/gofr/http/middleware/oauth.go
+++ b/pkg/gofr/http/middleware/oauth.go
@@ -114,7 +114,7 @@ type PublicKeyProvider interface {
 }
 
 // OAuth is a middleware function that validates JWT access tokens using a provided PublicKeyProvider.
-func OAuth(key PublicKeyProvider) func(inner http.Handler) http.Handler {
+func OAuth(key PublicKeyProvider, options ...jwt.ParserOption) func(http.Handler) http.Handler {
 	return func(inner http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			if isWellKnown(r.URL.Path) {
@@ -122,27 +122,40 @@ func OAuth(key PublicKeyProvider) func(inner http.Handler) http.Handler {
 				return
 			}
 
-			tokenString, err := extractToken(r.Header.Get("Authorization"))
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusUnauthorized)
-				return
-			}
+			options = append(options, jwt.WithIssuedAt())
 
-			token, err := parseToken(tokenString, key)
+			claims, err := processToken(r.Header.Get("Authorization"), key, options...)
 			if err != nil {
 				http.Error(w, err.Error(), http.StatusUnauthorized)
 				return
 			}
 
-			ctx := context.WithValue(r.Context(), JWTClaim, token.Claims)
-			*r = *r.Clone(ctx)
-
-			inner.ServeHTTP(w, r)
+			ctx := context.WithValue(r.Context(), JWTClaim, claims)
+			inner.ServeHTTP(w, r.Clone(ctx))
 		})
 	}
 }
 
-// ExtractToken validates the Authorization header and extracts the JWT token.
+func processToken(authHeader string, key PublicKeyProvider, opts ...jwt.ParserOption) (jwt.Claims, error) {
+	tokenString, err := extractToken(authHeader)
+	if err != nil {
+		return nil, err
+	}
+
+	token, err := parseToken(tokenString, key, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	claims, ok := token.Claims.(jwt.MapClaims)
+	if !ok {
+		return nil, jwt.ErrTokenInvalidClaims
+	}
+
+	return claims, nil
+}
+
+// extractToken validates the Authorization header and extracts the JWT token.
 func extractToken(authHeader string) (string, error) {
 	if authHeader == "" {
 		return "", errAuthorizationHeaderRequired
@@ -159,7 +172,7 @@ func extractToken(authHeader string) (string, error) {
 }
 
 // ParseToken parses the JWT token using the provided key provider.
-func parseToken(tokenString string, key PublicKeyProvider) (*jwt.Token, error) {
+func parseToken(tokenString string, key PublicKeyProvider, opts ...jwt.ParserOption) (*jwt.Token, error) {
 	return jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
 		kid := token.Header["kid"]
 		jwks := key.Get(fmt.Sprint(kid))
@@ -169,7 +182,7 @@ func parseToken(tokenString string, key PublicKeyProvider) (*jwt.Token, error) {
 		}
 
 		return jwks, nil
-	})
+	}, opts...)
 }
 
 // JWKS represents a JSON Web Key Set.



Test patch:
diff --git a/pkg/gofr/http/middleware/oauth_test.go b/pkg/gofr/http/middleware/oauth_test.go
index 24169ea9..916893f3 100644
--- a/pkg/gofr/http/middleware/oauth_test.go
+++ b/pkg/gofr/http/middleware/oauth_test.go
@@ -11,6 +11,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/golang-jwt/jwt/v5"
 	"github.com/gorilla/mux"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -25,19 +26,9 @@ func TestOAuthSuccess(t *testing.T) {
 
 	server := httptest.NewServer(router)
 
-	req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, server.URL+"/test", http.NoBody)
-	req.Header.Set("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjAwVFEwdlRpNVB1UnZscUZGY3dCeUc0WjBM"+
-		"dGREcUtJX0JWUFRrdnpleEUiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJzdGFnZS5rb3BzLmRldiIsImlhdCI6MTI1Nzg5NDAwMCwib3JpZyI6IkdP"+
-		"T0dMRSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLSjVEREE0enJ1ekZsc1E5S3ZMakhEdG"+
-		"JPVF9ocFZ6MGhFTzhqU2wybTdNeWs9czk2LWMiLCJzdWIiOiJyYWtzaGl0LnNpbmdoQHpvcHNtYXJ0LmNvbSIsInN1Yi1pZCI6ImE2NTczZTFkL"+
-		"WFiZWEtNDg2My1hY2RiLTZjZjM2MjZhNDQxNCIsInR5cCI6InJlZnJlc2hfdG9rZW4ifQ.NkYSi6KJtGA3js9dcN3UqJWfeJdB88p7cxclrc6"+
-		"fxJODlCalsbbwIr3QL4AR9i0ucJjmoTIipCwpdM1IYDjCd-ilf2mTp11Wba31XoH--8YLI9Ju0wbpYhtF3wa00NF1Ijt48ze09IJ6QtE-etm"+
-		"AN8T7izsXbPeSrFiN3NVQU87eGxc3bEQhEsV5u3E6j8EdVDv8xbwisETY-N0mDftZp0w8UCkQ7MarOrA5IaXs2MHyCETy5y9QFd4djppH9oFo"+
-		"y5-AtEZqzyHKfGMlerjtJp8uOgFso9FycGuO0TFhR4AaZGVZxB072Hu-71tbx7atXp3zmDdkK_jkg5aVepoU_Q")
-
 	client := http.Client{}
 
-	resp, err := client.Do(req)
+	resp, err := client.Do(getRequest(t, server.URL))
 
 	require.NoError(t, err)
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
@@ -71,19 +62,9 @@ func TestGetJwtClaims(t *testing.T) {
 
 	server := httptest.NewServer(router)
 
-	req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, server.URL+"/test", http.NoBody)
-	req.Header.Set("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjAwVFEwdlRpNVB1UnZscUZGY3dCeUc0WjBM"+
-		"dGREcUtJX0JWUFRrdnpleEUiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJzdGFnZS5rb3BzLmRldiIsImlhdCI6MTI1Nzg5NDAwMCwib3JpZyI6IkdP"+
-		"T0dMRSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLSjVEREE0enJ1ekZsc1E5S3ZMakhEdG"+
-		"JPVF9ocFZ6MGhFTzhqU2wybTdNeWs9czk2LWMiLCJzdWIiOiJyYWtzaGl0LnNpbmdoQHpvcHNtYXJ0LmNvbSIsInN1Yi1pZCI6ImE2NTczZTFkL"+
-		"WFiZWEtNDg2My1hY2RiLTZjZjM2MjZhNDQxNCIsInR5cCI6InJlZnJlc2hfdG9rZW4ifQ.NkYSi6KJtGA3js9dcN3UqJWfeJdB88p7cxclrc6"+
-		"fxJODlCalsbbwIr3QL4AR9i0ucJjmoTIipCwpdM1IYDjCd-ilf2mTp11Wba31XoH--8YLI9Ju0wbpYhtF3wa00NF1Ijt48ze09IJ6QtE-etm"+
-		"AN8T7izsXbPeSrFiN3NVQU87eGxc3bEQhEsV5u3E6j8EdVDv8xbwisETY-N0mDftZp0w8UCkQ7MarOrA5IaXs2MHyCETy5y9QFd4djppH9oFo"+
-		"y5-AtEZqzyHKfGMlerjtJp8uOgFso9FycGuO0TFhR4AaZGVZxB072Hu-71tbx7atXp3zmDdkK_jkg5aVepoU_Q")
-
 	client := http.Client{}
 
-	resp, err := client.Do(req)
+	resp, err := client.Do(getRequest(t, server.URL))
 	result := make([]byte, len(claims))
 	_, _ = resp.Body.Read(result)
 
@@ -234,20 +215,9 @@ func TestOAuthHTTPCallFailed(t *testing.T) {
 
 	server := httptest.NewServer(router)
 
-	req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, server.URL+"/test", http.NoBody)
-	req.Header.Set("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IklDbmFZdEwtSDExckl0WlJ4VVlLVElzbm"+
-		"5ybm1wWUp6cGFWRHVDRWN0Ukk9IiwidHlwIjoiSldUIn0.eyJhdWQiOiJzdGFnZS5rb3BzLmRldiIsImV4cCI6MTcxODc5MjQ2NiwiaWF0Ij"+
-		"oxNzEwMTUyNDY2LCJpc3MiOiJzdGFnZS5hdXRoLnpvcHNtYXJ0LmNvbSIsIm5hbWUiOiJSYWtzaGl0IFNpbmdoIiwib3JpZyI6IkdPT0dMRSI"+
-		"sInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLSjVEREE0enJ1ekZsc1E5S3ZMakhEdGJPVF9o"+
-		"cFZ6MGhFTzhqU2wybTdNeWs9czk2LWMiLCJzdWIiOiJyYWtzaGl0LnNpbmdoQHpvcHNtYXJ0LmNvbSIsInN1Yi1pZCI6ImE2NTczZTFkLWFiZW"+
-		"EtNDg2My1hY2RiLTZjZjM2MjZhNDQxNCIsInR5cCI6InJlZnJlc2hfdG9rZW4ifQ.eoRVSFcyvbWk-fUSlACI4pWwHcuwjA1BbKlYA_aEJA6T"+
-		"BRcnM0HoaxL_GcF0Q-95Z6Medk9l5Fe-zuY4xmLX0XRnA9y9KEsXvyhxsmLJTV32C2kirDh6TR5FIep3EKV0VdWKJT6LziBjrCP-F0pKb34em"+
-		"Ua7gsyi5OnkX12_ZcGpQpSbL3mcZpEEGUmKijlg1VspK4G9dTmNSUXofxStokxacLwa3hiFfkd7vtegkF79bfWPVm0hlJDGDcU7szUaIyHjdW"+
-		"rlUGqQ0A8-8dYQ-Z1o5STZITcxvSv6SaZNo08r_szi-TDLXRhASP3ojEjFCqFBmPw9HPxHG4JmV3SX2A")
-
 	client := http.Client{}
 
-	resp, err := client.Do(req)
+	resp, err := client.Do(getRequest(t, server.URL))
 
 	require.NoError(t, err)
 	assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
@@ -264,20 +234,9 @@ func TestOAuthReadError(t *testing.T) {
 
 	server := httptest.NewServer(router)
 
-	req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, server.URL+"/test", http.NoBody)
-	req.Header.Set("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IklDbmFZdEwtSDExckl0WlJ4VVlLVElzbm"+
-		"5ybm1wWUp6cGFWRHVDRWN0Ukk9IiwidHlwIjoiSldUIn0.eyJhdWQiOiJzdGFnZS5rb3BzLmRldiIsImV4cCI6MTcxODc5MjQ2NiwiaWF0Ij"+
-		"oxNzEwMTUyNDY2LCJpc3MiOiJzdGFnZS5hdXRoLnpvcHNtYXJ0LmNvbSIsIm5hbWUiOiJSYWtzaGl0IFNpbmdoIiwib3JpZyI6IkdPT0dMRSI"+
-		"sInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLSjVEREE0enJ1ekZsc1E5S3ZMakhEdGJPVF9o"+
-		"cFZ6MGhFTzhqU2wybTdNeWs9czk2LWMiLCJzdWIiOiJyYWtzaGl0LnNpbmdoQHpvcHNtYXJ0LmNvbSIsInN1Yi1pZCI6ImE2NTczZTFkLWFiZW"+
-		"EtNDg2My1hY2RiLTZjZjM2MjZhNDQxNCIsInR5cCI6InJlZnJlc2hfdG9rZW4ifQ.eoRVSFcyvbWk-fUSlACI4pWwHcuwjA1BbKlYA_aEJA6T"+
-		"BRcnM0HoaxL_GcF0Q-95Z6Medk9l5Fe-zuY4xmLX0XRnA9y9KEsXvyhxsmLJTV32C2kirDh6TR5FIep3EKV0VdWKJT6LziBjrCP-F0pKb34em"+
-		"Ua7gsyi5OnkX12_ZcGpQpSbL3mcZpEEGUmKijlg1VspK4G9dTmNSUXofxStokxacLwa3hiFfkd7vtegkF79bfWPVm0hlJDGDcU7szUaIyHjdW"+
-		"rlUGqQ0A8-8dYQ-Z1o5STZITcxvSv6SaZNo08r_szi-TDLXRhASP3ojEjFCqFBmPw9HPxHG4JmV3SX2A")
-
 	client := http.Client{}
 
-	resp, err := client.Do(req)
+	resp, err := client.Do(getRequest(t, server.URL))
 
 	require.NoError(t, err)
 	assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
@@ -294,20 +253,9 @@ func TestOAuthJSONUnmarshalError(t *testing.T) {
 
 	server := httptest.NewServer(router)
 
-	req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, server.URL+"/test", http.NoBody)
-	req.Header.Set("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IklDbmFZdEwtSDExckl0WlJ4VVlLVElzbm"+
-		"5ybm1wWUp6cGFWRHVDRWN0Ukk9IiwidHlwIjoiSldUIn0.eyJhdWQiOiJzdGFnZS5rb3BzLmRldiIsImV4cCI6MTcxODc5MjQ2NiwiaWF0Ij"+
-		"oxNzEwMTUyNDY2LCJpc3MiOiJzdGFnZS5hdXRoLnpvcHNtYXJ0LmNvbSIsIm5hbWUiOiJSYWtzaGl0IFNpbmdoIiwib3JpZyI6IkdPT0dMRSI"+
-		"sInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLSjVEREE0enJ1ekZsc1E5S3ZMakhEdGJPVF9o"+
-		"cFZ6MGhFTzhqU2wybTdNeWs9czk2LWMiLCJzdWIiOiJyYWtzaGl0LnNpbmdoQHpvcHNtYXJ0LmNvbSIsInN1Yi1pZCI6ImE2NTczZTFkLWFiZW"+
-		"EtNDg2My1hY2RiLTZjZjM2MjZhNDQxNCIsInR5cCI6InJlZnJlc2hfdG9rZW4ifQ.eoRVSFcyvbWk-fUSlACI4pWwHcuwjA1BbKlYA_aEJA6T"+
-		"BRcnM0HoaxL_GcF0Q-95Z6Medk9l5Fe-zuY4xmLX0XRnA9y9KEsXvyhxsmLJTV32C2kirDh6TR5FIep3EKV0VdWKJT6LziBjrCP-F0pKb34em"+
-		"Ua7gsyi5OnkX12_ZcGpQpSbL3mcZpEEGUmKijlg1VspK4G9dTmNSUXofxStokxacLwa3hiFfkd7vtegkF79bfWPVm0hlJDGDcU7szUaIyHjdW"+
-		"rlUGqQ0A8-8dYQ-Z1o5STZITcxvSv6SaZNo08r_szi-TDLXRhASP3ojEjFCqFBmPw9HPxHG4JmV3SX2A")
-
 	client := http.Client{}
 
-	resp, err := client.Do(req)
+	resp, err := client.Do(getRequest(t, server.URL))
 
 	require.NoError(t, err)
 	assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
@@ -408,3 +356,80 @@ func (*MockJSONResponseErrorProvider) GetWithHeaders(context.Context, string, ma
 
 	return response, nil
 }
+
+func Test_OAuthFailureInvalidAudience(t *testing.T) {
+	router := mux.NewRouter()
+
+	router.HandleFunc("/test", func(w http.ResponseWriter, _ *http.Request) {
+		w.WriteHeader(http.StatusOK)
+	}).Methods(http.MethodGet)
+
+	mockProvider := &MockProvider{}
+	parserOpts := []jwt.ParserOption{
+		jwt.WithIssuer("GOOGLE"),
+		jwt.WithAudience("stagekopsdev"),
+		jwt.WithValidMethods([]string{"RS256"}),
+		jwt.WithExpirationRequired(),
+		jwt.WithIssuedAt(),
+	}
+
+	router.Use(OAuth(NewOAuth(OauthConfigs{
+		Provider:        mockProvider,
+		RefreshInterval: 10,
+	}), parserOpts...))
+
+	server := httptest.NewServer(router)
+	defer server.Close()
+
+	client := http.Client{}
+
+	resp, err := client.Do(getRequest(t, server.URL))
+
+	require.NoError(t, err)
+
+	defer resp.Body.Close()
+
+	assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+}
+
+func Test_OAuthSuccessWithValidation(t *testing.T) {
+	router := mux.NewRouter()
+	router.HandleFunc("/test", func(w http.ResponseWriter, _ *http.Request) {
+		w.WriteHeader(http.StatusOK)
+	}).Methods(http.MethodGet).Name("/test")
+
+	parserOpts := []jwt.ParserOption{
+		jwt.WithAudience("stage.kops.dev"),
+		jwt.WithValidMethods([]string{"RS256"}),
+		jwt.WithIssuedAt(),
+	}
+
+	router.Use(OAuth(NewOAuth(OauthConfigs{Provider: &MockProvider{}, RefreshInterval: 10}), parserOpts...))
+
+	server := httptest.NewServer(router)
+
+	client := http.Client{}
+
+	resp, err := client.Do(getRequest(t, server.URL))
+
+	require.NoError(t, err)
+	assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+	resp.Body.Close()
+}
+
+func getRequest(t *testing.T, url string) *http.Request {
+	t.Helper()
+
+	req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, url+"/test", http.NoBody)
+	req.Header.Set("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjAwVFEwdlRpNVB1UnZscUZGY3dCeUc0WjBM"+
+		"dGREcUtJX0JWUFRrdnpleEUiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJzdGFnZS5rb3BzLmRldiIsImlhdCI6MTI1Nzg5NDAwMCwib3JpZyI6IkdP"+
+		"T0dMRSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLSjVEREE0enJ1ekZsc1E5S3ZMakhEdG"+
+		"JPVF9ocFZ6MGhFTzhqU2wybTdNeWs9czk2LWMiLCJzdWIiOiJyYWtzaGl0LnNpbmdoQHpvcHNtYXJ0LmNvbSIsInN1Yi1pZCI6ImE2NTczZTFkL"+
+		"WFiZWEtNDg2My1hY2RiLTZjZjM2MjZhNDQxNCIsInR5cCI6InJlZnJlc2hfdG9rZW4ifQ.NkYSi6KJtGA3js9dcN3UqJWfeJdB88p7cxclrc6"+
+		"fxJODlCalsbbwIr3QL4AR9i0ucJjmoTIipCwpdM1IYDjCd-ilf2mTp11Wba31XoH--8YLI9Ju0wbpYhtF3wa00NF1Ijt48ze09IJ6QtE-etm"+
+		"AN8T7izsXbPeSrFiN3NVQU87eGxc3bEQhEsV5u3E6j8EdVDv8xbwisETY-N0mDftZp0w8UCkQ7MarOrA5IaXs2MHyCETy5y9QFd4djppH9oFo"+
+		"y5-AtEZqzyHKfGMlerjtJp8uOgFso9FycGuO0TFhR4AaZGVZxB072Hu-71tbx7atXp3zmDdkK_jkg5aVepoU_Q")
+
+	return req
+}