Skip to content
This repository has been archived by the owner on May 24, 2024. It is now read-only.

Commit

Permalink
Merge pull request #11 from LF-Engineering/DA-4596-auth0-jwks-cache
Browse files Browse the repository at this point in the history
DA-4596: update for auth0 jwks cache changes
  • Loading branch information
ajinkyan83 authored Dec 21, 2021
2 parents 100832f + 30c7be9 commit 0c68810
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 53 deletions.
39 changes: 39 additions & 0 deletions auth0/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ type AuthToken struct {
CreatedAt time.Time `json:"created_at"`
}

// AuthJwks Struct
type AuthJwks struct {
Name string `json:"name"`
Jwks string `json:"jwks"`
CreatedAt time.Time `json:"created_at"`
}

// Resp struct
type Resp struct {
AccessToken string `json:"access_token"`
Expand Down Expand Up @@ -46,6 +53,36 @@ type ESTokenSchema struct {
} `json:"hits"`
}

// ESJwksSchema ...
type ESJwksSchema struct {
Took int `json:"took"`
TimedOut bool `json:"timed_out"`
Shards struct {
Total int `json:"total"`
Successful int `json:"successful"`
Skipped int `json:"skipped"`
Failed int `json:"failed"`
} `json:"_shards"`
Hits struct {
Total struct {
Value int `json:"value"`
Relation string `json:"relation"`
} `json:"total"`
MaxScore float64 `json:"max_score"`
Hits []struct {
Index string `json:"_index"`
Type string `json:"_type"`
ID string `json:"_id"`
Score float64 `json:"_score"`
Source struct {
Name string `json:"name"`
Jwks string `json:"jwks"`
CreatedAt time.Time `json:"created_at"`
} `json:"_source"`
} `json:"hits"`
} `json:"hits"`
}

// LastActionSchema ...
type LastActionSchema struct {
Took int `json:"took"`
Expand Down Expand Up @@ -79,6 +116,8 @@ const (
lastAuth0TokenRequest = "last-auth0-token-request-"
auth0TokenCache = "auth0-token-cache-"
tokenDoc = "token"
auth0JwksCache = "auth0-jwks-cache-"
jwksDoc = "jwks"
)

// RefreshResult ...
Expand Down
120 changes: 120 additions & 0 deletions auth0/jwks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package auth0

import (
"encoding/json"
"errors"
"fmt"
"log"
"strings"
"time"

"github.com/dgrijalva/jwt-go"
)

// Jwks result from auth0 well know keys
type Jwks struct {
Keys []JSONWebKeys `json:"keys"`
}

// JSONWebKeys auth0 token key
type JSONWebKeys struct {
Kty string `json:"kty"`
Kid string `json:"kid"`
Use string `json:"use"`
N string `json:"n"`
E string `json:"e"`
X5c []string `json:"x5c"`
}

func (a *ClientProvider) createAuthJwks(cert string) error {
log.Println("creating new auth jwks cert string")
at := AuthJwks{
Name: "AuthJwks",
Jwks: cert,
CreatedAt: time.Now().UTC(),
}
_, err := a.esClient.UpdateDocument(fmt.Sprintf("%s%s", auth0JwksCache, a.Environment), jwksDoc, at)
if err != nil {
log.Println("could not write the data", err)
return err
}

return nil
}

func (a *ClientProvider) getPemCert(token *jwt.Token, refreshJwks bool) (string, error) {
cert := ""
cert, err := a.getCachedJwks()
if err != nil {
return cert, err
}

// check if the refresh jwks cache flag coming from the refresh cron is set to true
if refreshJwks {
_, resp, err := a.httpClient.Request(fmt.Sprintf("%s/oauth/.well-known/jwks.json", a.AuthURL), "GET", nil, nil, nil)
if err != nil {
return cert, err
}

var jwks = Jwks{}
if err := json.Unmarshal(resp, &jwks); err != nil {
return cert, err
}

for _, k := range jwks.Keys {
if token.Header["kid"] == k.Kid {
cert = "-----BEGIN CERTIFICATE-----\n" + k.X5c[0] + "\n-----END CERTIFICATE-----"
}
}

if cert == "" {
err := errors.New("unable to find appropriate key")
return cert, err
}

err = a.createAuthJwks(cert)
if err != nil {
return "", err
}

}

return cert, nil
}

func (a *ClientProvider) getCachedJwks() (string, error) {
res, err := a.esClient.Search(strings.TrimSpace(auth0JwksCache+a.Environment), searchJwksQuery)
if err != nil {
go func() {
errMsg := fmt.Sprintf("%s-%s: error cached jwks not found\n %s", a.appName, a.Environment, err)
err := a.slackClient.SendText(errMsg)
fmt.Println("Err: send to slack: ", err)
}()

return "", err
}

var e ESJwksSchema
err = json.Unmarshal(res, &e)
if err != nil {
log.Println("repository: GetOauthJwks: could not unmarshal the data", err)
return "", err
}

if len(e.Hits.Hits) > 0 {
data := e.Hits.Hits[0]

return data.Source.Jwks, nil
}

return "", errors.New("GetJwks: could not find the associated jwks")
}

var searchJwksQuery = map[string]interface{}{
"size": 1,
"query": map[string]interface{}{
"term": map[string]interface{}{
"_id": jwksDoc,
},
},
}
72 changes: 23 additions & 49 deletions auth0/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/LF-Engineering/insights-datasource-shared/elastic"

"github.com/dgrijalva/jwt-go"
)

Expand Down Expand Up @@ -86,8 +87,13 @@ func (a *ClientProvider) GetToken() (string, error) {
}

// check token validity
ok, _, err := a.isValid(authToken)
if ok {
ok, _, err := a.isValid(authToken, false)
if err != nil {
log.Println(err)
return "", err
}

if ok && err == nil {
return authToken, nil
}

Expand Down Expand Up @@ -129,6 +135,7 @@ func (a *ClientProvider) generateToken() (string, error) {
}()
log.Println("Err: GenerateToken ", err)
}

go func() {
err = a.createLastActionDate()
log.Println(err)
Expand All @@ -141,7 +148,8 @@ func (a *ClientProvider) generateToken() (string, error) {
if result.AccessToken != "" {
log.Println("GenerateToken: Token generated successfully.")
}
ok, _, err := a.isValid(result.AccessToken)

ok, _, err := a.isValid(result.AccessToken, true)
if !ok || err != nil {
go func() {
errMsg := fmt.Sprintf("%s-%s: error validating the newly created token\n %s", a.appName, a.Environment, err)
Expand Down Expand Up @@ -214,13 +222,13 @@ var searchCacheQuery = map[string]interface{}{
},
}

func (a *ClientProvider) isValid(token string) (bool, jwt.MapClaims, error) {
func (a *ClientProvider) isValid(token string, refreshJwks bool) (bool, jwt.MapClaims, error) {
p, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
return nil, errors.New("unexpected signing method")
}

cert, err := a.getPemCert(t)
cert, err := a.getPemCert(t, refreshJwks)
if err != nil {
return nil, err
}
Expand All @@ -232,6 +240,7 @@ func (a *ClientProvider) isValid(token string) (bool, jwt.MapClaims, error) {

return key, nil
})

if err != nil {
return false, nil, err
}
Expand All @@ -244,47 +253,6 @@ func (a *ClientProvider) isValid(token string) (bool, jwt.MapClaims, error) {
return p.Valid, claims, err
}

// Jwks result from auth0 well know keys
type Jwks struct {
Keys []JSONWebKeys `json:"keys"`
}

// JSONWebKeys auth0 token key
type JSONWebKeys struct {
Kty string `json:"kty"`
Kid string `json:"kid"`
Use string `json:"use"`
N string `json:"n"`
E string `json:"e"`
X5c []string `json:"x5c"`
}

func (a *ClientProvider) getPemCert(token *jwt.Token) (string, error) {
cert := ""
_, resp, err := a.httpClient.Request(fmt.Sprintf("%s/oauth/.well-known/jwks.json", a.AuthURL), "GET", nil, nil, nil)
if err != nil {
return cert, err
}

var jwks = Jwks{}
if err := json.Unmarshal(resp, &jwks); err != nil {
return cert, err
}

for _, k := range jwks.Keys {
if token.Header["kid"] == k.Kid {
cert = "-----BEGIN CERTIFICATE-----\n" + k.X5c[0] + "\n-----END CERTIFICATE-----"
}
}

if cert == "" {
err := errors.New("unable to find appropriate key")
return cert, err
}

return cert, nil
}

func (a *ClientProvider) createLastActionDate() error {
s := struct {
Date time.Time `json:"date"`
Expand Down Expand Up @@ -349,22 +317,28 @@ func (a *ClientProvider) RefreshToken() (RefreshResult, error) {
}

if authToken == "" || err != nil {
authToken, err = a.refreshCachedToken()
_, err = a.refreshCachedToken()
if err != nil {
return RefreshError, err
}

return RefreshSuccessful, nil
}

ok, claims, err := a.isValid(authToken)
ok, claims, err := a.isValid(authToken, false)
if ok && err == nil {
if claims.VerifyExpiresAt(time.Now().Add(60*time.Minute).Unix(), false) == false {
if !claims.VerifyExpiresAt(time.Now().Add(60*time.Minute).Unix(), false) {
if _, err := a.refreshCachedToken(); err != nil {
log.Printf("Error refresh auth0 token %s\n", err.Error())
return RefreshError, err
}
if _, err := a.refreshCachedToken(); err != nil {
log.Printf("Error refresh auth0 token %s\n", err.Error())
return RefreshError, err
}
return RefreshSuccessful, nil
}

return NotExpireSoon, nil
}

Expand Down
13 changes: 12 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@ module github.com/LF-Engineering/insights-datasource-shared
go 1.15

require (
github.com/aws/aws-sdk-go-v2 v1.8.0
github.com/LF-Engineering/dev-analytics-libraries v1.1.28
github.com/avast/retry-go v3.0.0+incompatible
github.com/aws/aws-sdk-go v1.42.24
github.com/aws/aws-sdk-go-v2 v1.11.2
github.com/aws/aws-sdk-go-v2/config v1.6.0
github.com/aws/aws-sdk-go-v2/service/firehose v1.4.2
github.com/aws/aws-sdk-go-v2/service/ssm v1.17.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/elastic/go-elasticsearch/v8 v8.0.0-20211220171217-6cdebcf1b94e
github.com/google/uuid v1.3.0
github.com/json-iterator/go v1.1.11
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.6.1
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/text v0.3.7
gopkg.in/resty.v1 v1.12.0
)
Loading

0 comments on commit 0c68810

Please sign in to comment.