Skip to content

Commit

Permalink
Merge pull request #74 from okta/address_interface_slice
Browse files Browse the repository at this point in the history
Address interface slice
  • Loading branch information
monde authored Jan 24, 2022
2 parents 628a050 + ead12cb commit 0f90b2d
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 31 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@
### Updates

- Only `alg` and `kid` claims in a JWT header are considered during verification.

## v1.1.3

### Updates

- Fixed edge cause with `aud` claim that would not find Auth0 being JWTs valid (thank you @awrenn).
- Updated readme with testing notes.
- Ran `gofumpt` on code for clean up.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,23 @@ verifier.SetLeeway("2m") //String instance of time that will be parsed by `time.
```

[Okta Developer Forum]: https://devforum.okta.com/

## Testing

If you create a PR from a fork of okta/okta-jwt-verifier-golang the build for
the PR will fail. Don't worry, we'll bring your commits into a review branch in
okta/okta-jwt-verifier-golang and get a green build.

jwtverifier_test.go expects environment variables for `ISSUER`, `CLIENT_ID`,
`USERNAME`, and `PASSWORD` to be present. Take note if you use zshell as
`USERSNAME` is a special environment variable and is not settable. Therefore
tests shouldn't be run in zshell.

`USERNAME` and `PASSWORD` are for a user with access to the test app associated
with `CLIENT_ID`. The test app should not have 2FA enabled and allow password
login. The General Settings for the test app should have Application Grant type
with Implicit (hybrid) enabled.

```
go test -test.v
```
11 changes: 4 additions & 7 deletions adaptors/lestrratGoJwx/lestrratGoJwx.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import (
"github.com/patrickmn/go-cache"
)

var jwkSetCache *cache.Cache = cache.New(5*time.Minute, 10*time.Minute)
var jwkSetMu = &sync.Mutex{}
var (
jwkSetCache *cache.Cache = cache.New(5*time.Minute, 10*time.Minute)
jwkSetMu = &sync.Mutex{}
)

func getJwkSet(jwkUri string) (jwk.Set, error) {
jwkSetMu.Lock()
Expand All @@ -39,7 +41,6 @@ func getJwkSet(jwkUri string) (jwk.Set, error) {
return x.(jwk.Set), nil
}
jwkSet, err := jwk.Fetch(context.Background(), jwkUri)

if err != nil {
return nil, err
}
Expand All @@ -58,17 +59,14 @@ func (lgj LestrratGoJwx) New() adaptors.Adaptor {
}

func (lgj LestrratGoJwx) GetKey(jwkUri string) {
return
}

func (lgj LestrratGoJwx) Decode(jwt string, jwkUri string) (interface{}, error) {
jwkSet, err := getJwkSet(jwkUri)

if err != nil {
return nil, err
}
token, err := jws.VerifySet([]byte(jwt), jwkSet)

if err != nil {
return nil, err
}
Expand All @@ -78,5 +76,4 @@ func (lgj LestrratGoJwx) Decode(jwt string, jwkUri string) (interface{}, error)
json.Unmarshal(token, &claims)

return claims, nil

}
2 changes: 1 addition & 1 deletion discovery/oidc/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package oidc

import "github.com/okta/okta-jwt-verifier-golang/discovery"

type Oidc struct{
type Oidc struct {
wellKnownUrl string
}

Expand Down
38 changes: 23 additions & 15 deletions jwtverifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ import (
"github.com/patrickmn/go-cache"
)

var metaDataCache *cache.Cache = cache.New(5*time.Minute, 10*time.Minute)
var metaDataMu = &sync.Mutex{}
var regx = regexp.MustCompile(`[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.?([a-zA-Z0-9-_]+)[/a-zA-Z0-9-_]+?$`)
var (
metaDataCache *cache.Cache = cache.New(5*time.Minute, 10*time.Minute)
metaDataMu = &sync.Mutex{}
regx = regexp.MustCompile(`[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.?([a-zA-Z0-9-_]+)[/a-zA-Z0-9-_]+?$`)
)

type JwtVerifier struct {
Issuer string
Expand Down Expand Up @@ -80,7 +82,7 @@ func (j *JwtVerifier) SetLeeway(duration string) {

func (j *JwtVerifier) VerifyAccessToken(jwt string) (*Jwt, error) {
validJwt, err := j.isValidJwt(jwt)
if validJwt == false {
if !validJwt {
return nil, fmt.Errorf("token is not valid: %s", err.Error())
}

Expand Down Expand Up @@ -133,7 +135,6 @@ func (j *JwtVerifier) decodeJwt(jwt string) (interface{}, error) {
return nil, fmt.Errorf("failed to decode JWT: missing 'jwks_uri' from metadata")
}
resp, err := j.Adaptor.Decode(jwt, jwksURI)

if err != nil {
return nil, fmt.Errorf("could not decode token: %s", err.Error())
}
Expand All @@ -143,7 +144,7 @@ func (j *JwtVerifier) decodeJwt(jwt string) (interface{}, error) {

func (j *JwtVerifier) VerifyIdToken(jwt string) (*Jwt, error) {
validJwt, err := j.isValidJwt(jwt)
if validJwt == false {
if !validJwt {
return nil, fmt.Errorf("token is not valid: %s", err.Error())
}

Expand Down Expand Up @@ -206,7 +207,6 @@ func (j *JwtVerifier) validateNonce(nonce interface{}) error {
}

func (j *JwtVerifier) validateAudience(audience interface{}) error {

switch v := audience.(type) {
case string:
if v != j.ClaimsToValidate["aud"] {
Expand All @@ -219,8 +219,19 @@ func (j *JwtVerifier) validateAudience(audience interface{}) error {
}
}
return fmt.Errorf("aud: %s does not match %s", v, j.ClaimsToValidate["aud"])
case []interface{}:
for _, e := range v {
element, ok := e.(string)
if !ok {
return fmt.Errorf("unknown type for audience validation")
}
if element == j.ClaimsToValidate["aud"] {
return nil
}
}
return fmt.Errorf("aud: %s does not match %s", v, j.ClaimsToValidate["aud"])
default:
return fmt.Errorf("Unknown type for audience validation")
return fmt.Errorf("unknown type for audience validation")
}

return nil
Expand All @@ -229,7 +240,6 @@ func (j *JwtVerifier) validateAudience(audience interface{}) error {
func (j *JwtVerifier) validateClientId(clientId interface{}) error {
// Client Id can be optional, it will be validated if it is present in the ClaimsToValidate array
if cid, exists := j.ClaimsToValidate["cid"]; exists && clientId != cid {

switch v := clientId.(type) {
case string:
if v != cid {
Expand All @@ -243,9 +253,8 @@ func (j *JwtVerifier) validateClientId(clientId interface{}) error {
}
return fmt.Errorf("aud: %s does not match %s", v, cid)
default:
return fmt.Errorf("Unknown type for clientId validation")
return fmt.Errorf("unknown type for clientId validation")
}

}
return nil
}
Expand Down Expand Up @@ -290,7 +299,6 @@ func (j *JwtVerifier) getMetaData() (map[string]interface{}, error) {
}

resp, err := http.Get(metaDataUrl)

if err != nil {
return nil, fmt.Errorf("request for metadata was not successful: %s", err.Error())
}
Expand All @@ -311,7 +319,7 @@ func (j *JwtVerifier) isValidJwt(jwt string) (bool, error) {
}

// Verify that the JWT Follows correct JWT encoding.
var jwtRegex = regx.MatchString
jwtRegex := regx.MatchString
if !jwtRegex(jwt) {
return false, fmt.Errorf("token must contain at least 1 period ('.') and only characters 'a-Z 0-9 _'")
}
Expand All @@ -320,14 +328,13 @@ func (j *JwtVerifier) isValidJwt(jwt string) (bool, error) {
header := parts[0]
header = padHeader(header)
headerDecoded, err := base64.StdEncoding.DecodeString(header)

if err != nil {
return false, fmt.Errorf("the tokens header does not appear to be a base64 encoded string")
}

var jsonObject map[string]interface{}
isHeaderJson := json.Unmarshal([]byte(headerDecoded), &jsonObject) == nil
if isHeaderJson == false {
if !isHeaderJson {
return false, fmt.Errorf("the tokens header is not a json object")
}

Expand All @@ -348,6 +355,7 @@ func (j *JwtVerifier) isValidJwt(jwt string) (bool, error) {

return true, nil
}

func padHeader(header string) string {
if i := len(header) % 4; i != 0 {
header += strings.Repeat("=", 4-i)
Expand Down
4 changes: 1 addition & 3 deletions jwtverifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,11 @@ func Test_a_successful_authentication_can_have_its_tokens_parsed(t *testing.T) {
postValues := map[string]string{"username": os.Getenv("USERNAME"), "password": os.Getenv("PASSWORD")}
postJsonValues, _ := json.Marshal(postValues)
resp, err := http.Post(requestUri, "application/json", bytes.NewReader(postJsonValues))

if err != nil {
t.Errorf("could not submit authentication endpoint")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, _ := ioutil.ReadAll(resp.Body)

var authn AuthnResponse
err = json.Unmarshal(body, &authn)
Expand Down Expand Up @@ -406,7 +405,6 @@ func Test_a_successful_authentication_can_have_its_tokens_parsed(t *testing.T) {
}

claims, err := jv.New().VerifyIdToken(idToken)

if err != nil {
t.Errorf("could not verify id_token: %s", err.Error())
}
Expand Down
4 changes: 2 additions & 2 deletions utils/nonce.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package utils

import (
"fmt"
"encoding/base64"
"crypto/rand"
"encoding/base64"
"fmt"
)

func GenerateNonce() (string, error) {
Expand Down
4 changes: 1 addition & 3 deletions utils/parseEnv.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ import (
)

func ParseEnvironment() {
//useGlobalEnv := true
if _, err := os.Stat(".env"); os.IsNotExist(err) {
log.Printf("Environment Variable file (.env) is not present. Relying on Global Environment Variables")
//useGlobalEnv = false
}

setEnvVariable("CLIENT_ID", os.Getenv("CLIENT_ID"))
Expand All @@ -43,8 +41,8 @@ func ParseEnvironment() {
log.Printf("Could not resolve a ISSUER environment variable.")
os.Exit(1)
}

}

func setEnvVariable(env string, current string) {
if current != "" {
return
Expand Down

0 comments on commit 0f90b2d

Please sign in to comment.