Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(auth0): created_at workaround #2492

Merged
merged 1 commit into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 24 additions & 40 deletions selfservice/strategy/oidc/provider_auth0.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import (
"io/ioutil"
"net/url"
"path"
"strconv"
"time"

"github.com/tidwall/sjson"

"github.com/hashicorp/go-retryablehttp"

"github.com/ory/x/httpx"
Expand Down Expand Up @@ -92,51 +93,15 @@ func (g *ProviderAuth0) Claims(ctx context.Context, exchange *oauth2.Token, quer
}
defer resp.Body.Close()

// There is a bug in the response from Auth0. The updated_at field may be a string and not an int64.
// https://community.auth0.com/t/oidc-id-token-claim-updated-at-violates-oidc-specification-breaks-rp-implementations/24098
// We work around this by reading the json generically (as map[string]inteface{} and looking at the updated_at field
// if it exists. If it's the wrong type (string), we fill out the claims by hand.

// Once auth0 fixes this bug, all this workaround can be removed.
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err))
}

// Force updatedAt to be an int if given as a string in the response.
if updatedAtField := gjson.GetBytes(b, "updated_at"); updatedAtField.Exists() {
v := updatedAtField.Value()
switch v.(type) {
case string:
t, err := time.Parse(time.RFC3339, updatedAtField.String())
if err != nil {
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("bad time format in updated_at"))
}
updatedAt := t.Unix()

// Unmarshal into generic map, replace the updated_at value with the correct type, then re-marshal.
var data map[string]interface{}
err = json.Unmarshal(b, &data)
if err != nil {
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("bad type in response"))
}

// convert the correct int64 type back to a string, so we can Marshal it.
data["updated_at"] = strconv.FormatInt(updatedAt, 10)

// now remarshal so the unmarshal into Claims works.
b, err = json.Marshal(data)
if err != nil {
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err))
}

case float64:
// nothing to do
break

default:
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("bad updated_at type"))
}
b, err = authZeroUpdatedAtWorkaround(b)
if err != nil {
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err))
}

// Once we get here, we know that if there is an updated_at field in the json, it is the correct type.
Expand All @@ -147,3 +112,22 @@ func (g *ProviderAuth0) Claims(ctx context.Context, exchange *oauth2.Token, quer

return &claims, nil
}

// There is a bug in the response from Auth0. The updated_at field may be a string and not an int64.
// https://community.auth0.com/t/oidc-id-token-claim-updated-at-violates-oidc-specification-breaks-rp-implementations/24098
// We work around this by reading the json generically (as map[string]inteface{} and looking at the updated_at field
// if it exists. If it's the wrong type (string), we fill out the claims by hand.
func authZeroUpdatedAtWorkaround(body []byte) ([]byte, error) {
// Force updatedAt to be an int if given as a string in the response.
if updatedAtField := gjson.GetBytes(body, "updated_at"); updatedAtField.Exists() && updatedAtField.Type == gjson.String {
t, err := time.Parse(time.RFC3339, updatedAtField.String())
if err != nil {
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("bad time format in updated_at"))
}
body, err = sjson.SetBytes(body, "updated_at", t.Unix())
if err != nil {
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err))
}
}
return body, nil
}
30 changes: 30 additions & 0 deletions selfservice/strategy/oidc/provider_auth0_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package oidc

import (
"encoding/json"
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAuthZeroUpdatedAtWorkaround(t *testing.T) {
actual, err := authZeroUpdatedAtWorkaround([]byte("{}"))
require.NoError(t, err)
assert.Equal(t, "{}", string(actual))

actual, err = authZeroUpdatedAtWorkaround([]byte(`{"updated_at":1234}`))
require.NoError(t, err)
assert.Equal(t, `{"updated_at":1234}`, string(actual))

timestamp := time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
input, err := json.Marshal(map[string]interface{}{
"updated_at": timestamp,
})
require.NoError(t, err)
actual, err = authZeroUpdatedAtWorkaround(input)
require.NoError(t, err)
assert.Equal(t, fmt.Sprintf(`{"updated_at":%d}`, timestamp.Unix()), string(actual))
}