...

Source file src/github.com/okta/okta-jwt-verifier-golang/jwtverifier_test.go

Documentation: github.com/okta/okta-jwt-verifier-golang

     1  /*******************************************************************************
     2   * Copyright 2018 - Present Okta, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *      http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   ******************************************************************************/
    16  
    17  package jwtverifier
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"io/ioutil"
    23  	"log"
    24  	"net/http"
    25  	"net/url"
    26  	"os"
    27  	"reflect"
    28  	"strings"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/okta/okta-jwt-verifier-golang/adaptors/lestrratGoJwx"
    33  	"github.com/okta/okta-jwt-verifier-golang/discovery/oidc"
    34  	"github.com/okta/okta-jwt-verifier-golang/utils"
    35  )
    36  
    37  func Test_the_verifier_defaults_to_oidc_if_nothing_is_provided_for_discovery(t *testing.T) {
    38  	jvs := JwtVerifier{
    39  		Issuer: "issuer",
    40  	}
    41  
    42  	jv := jvs.New()
    43  
    44  	if reflect.TypeOf(jv.GetDiscovery()) != reflect.TypeOf(oidc.Oidc{}) {
    45  		t.Errorf("discovery did not set to oidc by default.  Was set to: %s",
    46  			reflect.TypeOf(jv.GetDiscovery()))
    47  	}
    48  }
    49  
    50  func Test_the_verifier_defaults_to_lestrratGoJwx_if_nothing_is_provided_for_adaptor(t *testing.T) {
    51  	jvs := JwtVerifier{
    52  		Issuer: "issuer",
    53  	}
    54  
    55  	jv := jvs.New()
    56  
    57  	if reflect.TypeOf(jv.GetAdaptor()) != reflect.TypeOf(&lestrratGoJwx.LestrratGoJwx{}) {
    58  		t.Errorf("adaptor did not set to lestrratGoJwx by default.  Was set to: %s",
    59  			reflect.TypeOf(jv.GetAdaptor()))
    60  	}
    61  }
    62  
    63  func Test_can_validate_iss_from_issuer_provided(t *testing.T) {
    64  	jvs := JwtVerifier{
    65  		Issuer: "https://golang.oktapreview.com",
    66  	}
    67  
    68  	jv := jvs.New()
    69  
    70  	err := jv.validateIss("test")
    71  	if err == nil {
    72  		t.Errorf("the issuer validation did not trigger an error")
    73  	}
    74  }
    75  
    76  func Test_can_validate_nonce(t *testing.T) {
    77  	tv := map[string]string{}
    78  	tv["nonce"] = "abc123"
    79  
    80  	jvs := JwtVerifier{
    81  		Issuer:           "https://golang.oktapreview.com",
    82  		ClaimsToValidate: tv,
    83  	}
    84  
    85  	jv := jvs.New()
    86  
    87  	err := jv.validateNonce("test")
    88  	if err == nil {
    89  		t.Errorf("the nonce validation did not trigger an error")
    90  	}
    91  }
    92  
    93  func Test_can_validate_aud(t *testing.T) {
    94  	tv := map[string]string{}
    95  	tv["aud"] = "abc123"
    96  
    97  	jvs := JwtVerifier{
    98  		Issuer:           "https://golang.oktapreview.com",
    99  		ClaimsToValidate: tv,
   100  	}
   101  
   102  	jv := jvs.New()
   103  
   104  	err := jv.validateAudience("test")
   105  	if err == nil {
   106  		t.Errorf("the audience validation did not trigger an error")
   107  	}
   108  }
   109  
   110  func Test_can_validate_cid(t *testing.T) {
   111  	tv := map[string]string{}
   112  	tv["cid"] = "abc123"
   113  
   114  	jvs := JwtVerifier{
   115  		Issuer:           "https://golang.oktapreview.com",
   116  		ClaimsToValidate: tv,
   117  	}
   118  
   119  	jv := jvs.New()
   120  
   121  	err := jv.validateClientId("test")
   122  	if err == nil {
   123  		t.Errorf("the cid validation did not trigger an error")
   124  	}
   125  }
   126  
   127  func Test_can_validate_iat(t *testing.T) {
   128  	jvs := JwtVerifier{
   129  		Issuer: "https://golang.oktapreview.com",
   130  	}
   131  
   132  	jv := jvs.New()
   133  
   134  	// token issued in future triggers error
   135  	err := jv.validateIat(float64(time.Now().Unix() + 300))
   136  	if err == nil {
   137  		t.Errorf("the iat validation did not trigger an error")
   138  	}
   139  
   140  	// token within leeway does not trigger error
   141  	err = jv.validateIat(float64(time.Now().Unix()))
   142  	if err != nil {
   143  		t.Errorf("the iat validation triggered an error")
   144  	}
   145  }
   146  
   147  func Test_can_validate_exp(t *testing.T) {
   148  	jvs := JwtVerifier{
   149  		Issuer: "https://golang.oktapreview.com",
   150  	}
   151  
   152  	jv := jvs.New()
   153  
   154  	// expired token triggers error
   155  	err := jv.validateExp(float64(time.Now().Unix() - 300))
   156  	if err == nil {
   157  		t.Errorf("the exp validation did not trigger an error for expired token")
   158  	}
   159  
   160  	// token within leeway does not trigger error
   161  	err = jv.validateExp(float64(time.Now().Unix()))
   162  	if err != nil {
   163  		t.Errorf("the exp validation triggered an error for valid token")
   164  	}
   165  }
   166  
   167  // ID TOKEN TESTS
   168  func Test_invalid_formatting_of_id_token_throws_an_error(t *testing.T) {
   169  	jvs := JwtVerifier{
   170  		Issuer: "https://golang.oktapreview.com",
   171  	}
   172  
   173  	jv := jvs.New()
   174  
   175  	_, err := jv.VerifyIdToken("aa")
   176  
   177  	if err == nil {
   178  		t.Errorf("an error was not thrown when an id token does not contain at least 1 period ('.')")
   179  	}
   180  
   181  	if !strings.Contains(err.Error(), "token must contain at least 1 period ('.')") {
   182  		t.Errorf("the error for id token with no periods did not trigger")
   183  	}
   184  }
   185  
   186  func Test_an_id_token_header_that_is_improperly_formatted_throws_an_error(t *testing.T) {
   187  	jvs := JwtVerifier{
   188  		Issuer: "https://golang.oktapreview.com",
   189  	}
   190  
   191  	jv := jvs.New()
   192  
   193  	_, err := jv.VerifyIdToken("123456789.aa.aa")
   194  
   195  	if !strings.Contains(err.Error(), "does not appear to be a base64 encoded string") {
   196  		t.Errorf("the error for id token with header that is not base64 encoded did not trigger")
   197  	}
   198  }
   199  
   200  func Test_an_id_token_header_that_is_not_decoded_into_json_throws_an_error(t *testing.T) {
   201  	jvs := JwtVerifier{
   202  		Issuer: "https://golang.oktapreview.com",
   203  	}
   204  
   205  	jv := jvs.New()
   206  
   207  	_, err := jv.VerifyIdToken("aa.aa.aa")
   208  
   209  	if !strings.Contains(err.Error(), "not a json object") {
   210  		t.Errorf("the error for id token with header that is not a json object did not trigger")
   211  	}
   212  }
   213  
   214  func Test_an_id_token_header_that_is_not_contain_the_correct_parts_throws_an_error(t *testing.T) {
   215  	jvs := JwtVerifier{
   216  		Issuer: "https://golang.oktapreview.com",
   217  	}
   218  
   219  	jv := jvs.New()
   220  
   221  	_, err := jv.VerifyIdToken("ew0KICAia2lkIjogImFiYzEyMyIsDQogICJhbmQiOiAidGhpcyINCn0.aa.aa")
   222  
   223  	if !strings.Contains(err.Error(), "header must contain an 'alg'") {
   224  		t.Errorf("the error for id token with header that did not contain alg did not trigger")
   225  	}
   226  
   227  	_, err = jv.VerifyIdToken("ew0KICAiYWxnIjogIlJTMjU2IiwNCiAgImFuZCI6ICJ0aGlzIg0KfQ.aa.aa")
   228  
   229  	if !strings.Contains(err.Error(), "header must contain a 'kid'") {
   230  		t.Errorf("the error for id token with header that did not contain kid did not trigger")
   231  	}
   232  }
   233  
   234  func Test_an_id_token_header_that_is_not_rs256_throws_an_error(t *testing.T) {
   235  	jvs := JwtVerifier{
   236  		Issuer: "https://golang.oktapreview.com",
   237  	}
   238  
   239  	jv := jvs.New()
   240  
   241  	_, err := jv.VerifyIdToken("ew0KICAia2lkIjogImFiYzEyMyIsDQogICJhbGciOiAiSFMyNTYiDQp9.aa.aa")
   242  
   243  	if !strings.Contains(err.Error(), "only supported alg is RS256") {
   244  		t.Errorf("the error for id token with with wrong alg did not trigger")
   245  	}
   246  }
   247  
   248  // ACCESS TOKEN TESTS
   249  func Test_invalid_formatting_of_access_token_throws_an_error(t *testing.T) {
   250  	jvs := JwtVerifier{
   251  		Issuer: "https://golang.oktapreview.com",
   252  	}
   253  
   254  	jv := jvs.New()
   255  
   256  	_, err := jv.VerifyAccessToken("aa")
   257  
   258  	if err == nil {
   259  		t.Errorf("an error was not thrown when an access token does not contain at least 1 period ('.')")
   260  	}
   261  
   262  	if !strings.Contains(err.Error(), "token must contain at least 1 period ('.')") {
   263  		t.Errorf("the error for access token with no periods did not trigger")
   264  	}
   265  }
   266  
   267  func Test_an_access_token_header_that_is_improperly_formatted_throws_an_error(t *testing.T) {
   268  	jvs := JwtVerifier{
   269  		Issuer: "https://golang.oktapreview.com",
   270  	}
   271  
   272  	jv := jvs.New()
   273  
   274  	_, err := jv.VerifyAccessToken("123456789.aa.aa")
   275  
   276  	if !strings.Contains(err.Error(), "does not appear to be a base64 encoded string") {
   277  		t.Errorf("the error for access token with header that is not base64 encoded did not trigger")
   278  	}
   279  }
   280  
   281  func Test_an_access_token_header_that_is_not_decoded_into_json_throws_an_error(t *testing.T) {
   282  	jvs := JwtVerifier{
   283  		Issuer: "https://golang.oktapreview.com",
   284  	}
   285  
   286  	jv := jvs.New()
   287  
   288  	_, err := jv.VerifyAccessToken("aa.aa.aa")
   289  
   290  	if !strings.Contains(err.Error(), "not a json object") {
   291  		t.Errorf("the error for access token with header that is not a json object did not trigger")
   292  	}
   293  }
   294  
   295  func Test_an_access_token_header_that_is_not_contain_the_correct_parts_throws_an_error(t *testing.T) {
   296  	jvs := JwtVerifier{
   297  		Issuer: "https://golang.oktapreview.com",
   298  	}
   299  
   300  	jv := jvs.New()
   301  
   302  	_, err := jv.VerifyAccessToken("ew0KICAia2lkIjogImFiYzEyMyIsDQogICJhbmQiOiAidGhpcyINCn0.aa.aa")
   303  
   304  	if !strings.Contains(err.Error(), "header must contain an 'alg'") {
   305  		t.Errorf("the error for access token with header that did not contain alg did not trigger")
   306  	}
   307  
   308  	_, err = jv.VerifyAccessToken("ew0KICAiYWxnIjogIlJTMjU2IiwNCiAgImFuZCI6ICJ0aGlzIg0KfQ.aa.aa")
   309  
   310  	if !strings.Contains(err.Error(), "header must contain a 'kid'") {
   311  		t.Errorf("the error for access token with header that did not contain kid did not trigger")
   312  	}
   313  }
   314  
   315  func Test_an_access_token_header_that_is_not_rs256_throws_an_error(t *testing.T) {
   316  	jvs := JwtVerifier{
   317  		Issuer: "https://golang.oktapreview.com",
   318  	}
   319  
   320  	jv := jvs.New()
   321  
   322  	_, err := jv.VerifyAccessToken("ew0KICAia2lkIjogImFiYzEyMyIsDQogICJhbGciOiAiSFMyNTYiDQp9.aa.aa")
   323  
   324  	if !strings.Contains(err.Error(), "only supported alg is RS256") {
   325  		t.Errorf("the error for access token with with wrong alg did not trigger")
   326  	}
   327  }
   328  
   329  func Test_a_successful_authentication_can_have_its_tokens_parsed(t *testing.T) {
   330  	utils.ParseEnvironment()
   331  
   332  	if os.Getenv("ISSUER") == "" || os.Getenv("CLIENT_ID") == "" {
   333  		log.Printf("Skipping integration tests")
   334  		t.Skip("appears that environment variables are not set, skipping the integration test for now")
   335  	}
   336  
   337  	type AuthnResponse struct {
   338  		SessionToken string `json:"sessionToken"`
   339  	}
   340  
   341  	nonce, err := utils.GenerateNonce()
   342  	if err != nil {
   343  		t.Errorf("could not generate nonce")
   344  	}
   345  
   346  	// Get Session Token
   347  	issuerParts, _ := url.Parse(os.Getenv("ISSUER"))
   348  	baseUrl := issuerParts.Scheme + "://" + issuerParts.Hostname()
   349  	requestUri := baseUrl + "/api/v1/authn"
   350  	postValues := map[string]string{"username": os.Getenv("USERNAME"), "password": os.Getenv("PASSWORD")}
   351  	postJsonValues, _ := json.Marshal(postValues)
   352  	resp, err := http.Post(requestUri, "application/json", bytes.NewReader(postJsonValues))
   353  	if err != nil {
   354  		t.Errorf("could not submit authentication endpoint")
   355  	}
   356  	defer resp.Body.Close()
   357  	body, _ := ioutil.ReadAll(resp.Body)
   358  
   359  	var authn AuthnResponse
   360  	err = json.Unmarshal(body, &authn)
   361  	if err != nil {
   362  		t.Errorf("could not unmarshal authn response")
   363  	}
   364  
   365  	// Issue get request with session token to get id/access tokens
   366  	authzUri := os.Getenv("ISSUER") + "/v1/authorize?client_id=" + os.Getenv(
   367  		"CLIENT_ID") + "&nonce=" + nonce + "&redirect_uri=http://localhost:8080/implicit/callback" +
   368  		"&response_type=token%20id_token&scope=openid&state" +
   369  		"=ApplicationState&sessionToken=" + authn.SessionToken
   370  
   371  	client := &http.Client{
   372  		CheckRedirect: func(req *http.Request, with []*http.Request) error {
   373  			return http.ErrUseLastResponse
   374  		},
   375  	}
   376  
   377  	resp, err = client.Get(authzUri)
   378  
   379  	if err != nil {
   380  		t.Errorf("could not submit authorization endpoint: %s", err.Error())
   381  	}
   382  
   383  	defer resp.Body.Close()
   384  	location := resp.Header.Get("Location")
   385  	locParts, _ := url.Parse(location)
   386  	fragmentParts, _ := url.ParseQuery(locParts.Fragment)
   387  
   388  	if fragmentParts["access_token"] == nil {
   389  		t.Errorf("could not extract access_token")
   390  	}
   391  
   392  	if fragmentParts["id_token"] == nil {
   393  		t.Errorf("could not extract id_token")
   394  	}
   395  
   396  	accessToken := fragmentParts["access_token"][0]
   397  	idToken := fragmentParts["id_token"][0]
   398  
   399  	tv := map[string]string{}
   400  	tv["aud"] = os.Getenv("CLIENT_ID")
   401  	tv["nonce"] = nonce
   402  	jv := JwtVerifier{
   403  		Issuer:           os.Getenv("ISSUER"),
   404  		ClaimsToValidate: tv,
   405  	}
   406  
   407  	claims, err := jv.New().VerifyIdToken(idToken)
   408  	if err != nil {
   409  		t.Errorf("could not verify id_token: %s", err.Error())
   410  	}
   411  
   412  	issuer := claims.Claims["iss"]
   413  
   414  	if issuer == nil {
   415  		t.Errorf("issuer claim could not be pulled from access_token")
   416  	}
   417  
   418  	tv = map[string]string{}
   419  	tv["aud"] = "api://default"
   420  	tv["cid"] = os.Getenv("CLIENT_ID")
   421  	jv = JwtVerifier{
   422  		Issuer:           os.Getenv("ISSUER"),
   423  		ClaimsToValidate: tv,
   424  	}
   425  
   426  	claims, err = jv.New().VerifyAccessToken(accessToken)
   427  
   428  	if err != nil {
   429  		t.Errorf("could not verify access_token: %s", err.Error())
   430  	}
   431  
   432  	issuer = claims.Claims["iss"]
   433  
   434  	if issuer == nil {
   435  		t.Errorf("issuer claim could not be pulled from access_token")
   436  	}
   437  
   438  	// Should validate without CID
   439  	tv = map[string]string{}
   440  	tv["aud"] = "api://default"
   441  	jv = JwtVerifier{
   442  		Issuer:           os.Getenv("ISSUER"),
   443  		ClaimsToValidate: tv,
   444  	}
   445  
   446  	claims, err = jv.New().VerifyAccessToken(accessToken)
   447  
   448  	if err != nil {
   449  		t.Errorf("could not verify access_token: %s", err.Error())
   450  	}
   451  
   452  	issuer = claims.Claims["iss"]
   453  
   454  	if issuer == nil {
   455  		t.Errorf("issuer claim could not be pulled from access_token")
   456  	}
   457  }
   458  

View as plain text