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

claims: add amr for authentication method #230

Merged
merged 2 commits into from
Dec 7, 2023
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
8 changes: 4 additions & 4 deletions app/services/session_creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ package services
import (
"github.com/keratin/authn-server/app"
"github.com/keratin/authn-server/app/data"
"github.com/keratin/authn-server/lib/route"
"github.com/keratin/authn-server/app/models"
"github.com/keratin/authn-server/ops"
"github.com/keratin/authn-server/app/tokens/identities"
"github.com/keratin/authn-server/app/tokens/sessions"
"github.com/keratin/authn-server/lib/route"
"github.com/keratin/authn-server/ops"
"github.com/pkg/errors"
)

func SessionCreator(
accountStore data.AccountStore, refreshTokenStore data.RefreshTokenStore, keyStore data.KeyStore, actives data.Actives, cfg *app.Config, reporter ops.ErrorReporter,
accountID int, audience *route.Domain, existingToken *models.RefreshToken,
accountID int, audience *route.Domain, existingToken *models.RefreshToken, amr []string,
) (string, string, error) {
var err error
err = SessionEnder(refreshTokenStore, existingToken)
Expand All @@ -36,7 +36,7 @@ func SessionCreator(
}

// create new session token
session, err := sessions.New(refreshTokenStore, cfg, accountID, audience.String())
session, err := sessions.New(refreshTokenStore, cfg, accountID, audience.String(), amr)
if err != nil {
return "", "", errors.Wrap(err, "sessions.New")
}
Expand Down
6 changes: 3 additions & 3 deletions app/services/session_creator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestSessionCreator(t *testing.T) {
t.Run("tracks last login while generating tokens", func(t *testing.T) {
identityToken, refreshToken, err := services.SessionCreator(
accountStore, refreshStore, keyStore, nil, cfg, reporter,
account.ID, audience, nil,
account.ID, audience, nil, nil,
)
assert.NoError(t, err)
assert.NotEmpty(t, identityToken)
Expand All @@ -48,7 +48,7 @@ func TestSessionCreator(t *testing.T) {
activesStore := mock.NewActives()
_, _, err := services.SessionCreator(
accountStore, refreshStore, keyStore, activesStore, cfg, reporter,
account.ID, audience, nil,
account.ID, audience, nil, nil,
)
require.NoError(t, err)

Expand All @@ -63,7 +63,7 @@ func TestSessionCreator(t *testing.T) {

_, _, err = services.SessionCreator(
accountStore, refreshStore, keyStore, nil, cfg, reporter,
account.ID, audience, &token,
account.ID, audience, &token, nil,
)
assert.NoError(t, err)

Expand Down
2 changes: 1 addition & 1 deletion app/services/session_refresher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestSessionRefresher(t *testing.T) {

accountID := 0
audience := &route.Domain{Hostname: "authn.example.com", Port: "8080"}
session, err := sessions.New(refreshStore, cfg, accountID, audience.String())
session, err := sessions.New(refreshStore, cfg, accountID, audience.String(), []string{"pwd"})
require.NoError(t, err)
assert.NotEmpty(t, session.SessionID)

Expand Down
10 changes: 6 additions & 4 deletions app/tokens/identities/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import (
// Note that this type is embedded in the go client when changing:
// https://github.com/keratin/authn-go/blob/master/authn/claims.go
type Claims struct {
AuthTime *jwt.NumericDate `json:"auth_time"`
SessionID string `json:"sid"`
AuthTime *jwt.NumericDate `json:"auth_time"`
SessionID string `json:"sid"`
AuthMethodReference []string `json:"amr"`
jwt.Claims
}

Expand All @@ -40,8 +41,9 @@ func (c *Claims) Sign(key *private.Key) (string, error) {

func New(cfg *app.Config, session *sessions.Claims, accountID int, audience string) *Claims {
return &Claims{
AuthTime: session.IssuedAt,
SessionID: session.SessionID,
AuthTime: session.IssuedAt,
SessionID: session.SessionID,
AuthMethodReference: session.AuthMethodReference,
Claims: jwt.Claims{
Issuer: session.Issuer,
Subject: strconv.Itoa(accountID),
Expand Down
2 changes: 1 addition & 1 deletion app/tokens/identities/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestIdentityClaims(t *testing.T) {
}
key, err := private.GenerateKey(512)
require.NoError(t, err)
session, err := sessions.New(store, &cfg, 1, "example.com")
session, err := sessions.New(store, &cfg, 1, "example.com", []string{"pwd"})
require.NoError(t, err)

t.Run("includes KID", func(t *testing.T) {
Expand Down
16 changes: 9 additions & 7 deletions app/tokens/sessions/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import (
const scope = "refresh"

type Claims struct {
Scope string `json:"scope"`
Azp string `json:"azp"`
SessionID string `json:"sid"`
Scope string `json:"scope"`
Azp string `json:"azp"`
SessionID string `json:"sid"`
AuthMethodReference []string `json:"amr"`
jwt.Claims
}

Expand Down Expand Up @@ -58,16 +59,17 @@ func Parse(tokenStr string, cfg *app.Config) (*Claims, error) {
return &claims, nil
}

func New(store data.RefreshTokenStore, cfg *app.Config, accountID int, authorizedAudience string) (*Claims, error) {
func New(store data.RefreshTokenStore, cfg *app.Config, accountID int, authorizedAudience string, amr []string) (*Claims, error) {
refreshToken, err := store.Create(accountID)
if err != nil {
return nil, errors.Wrap(err, "Create")
}

return &Claims{
Scope: scope,
Azp: authorizedAudience,
SessionID: uuid.NewString(),
Scope: scope,
Azp: authorizedAudience,
SessionID: uuid.NewString(),
AuthMethodReference: amr,
Claims: jwt.Claims{
Issuer: cfg.AuthNURL.String(),
Subject: string(refreshToken),
Expand Down
6 changes: 3 additions & 3 deletions app/tokens/sessions/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestNewAndParseAndSign(t *testing.T) {
SessionSigningKey: []byte("key-a-reno"),
}

token, err := sessions.New(store, &cfg, 658908, "example.com")
token, err := sessions.New(store, &cfg, 658908, "example.com", []string{"pwd"})
require.NoError(t, err)
assert.Equal(t, "refresh", token.Scope)
assert.Equal(t, "http://authn.example.com", token.Issuer)
Expand Down Expand Up @@ -51,7 +51,7 @@ func TestParseInvalidSessionJWT(t *testing.T) {
cfg := app.Config{AuthNURL: &authn, SessionSigningKey: key}

t.Run("old key", func(t *testing.T) {
token, err := sessions.New(store, &app.Config{AuthNURL: &authn}, 1, mainApp.Host)
token, err := sessions.New(store, &app.Config{AuthNURL: &authn}, 1, mainApp.Host, []string{"pwd"})
require.NoError(t, err)
tokenStr, err := token.Sign([]byte("old key"))
require.NoError(t, err)
Expand All @@ -61,7 +61,7 @@ func TestParseInvalidSessionJWT(t *testing.T) {
})

t.Run("different audience", func(t *testing.T) {
token, err := sessions.New(store, &app.Config{AuthNURL: &authn}, 2, mainApp.Host)
token, err := sessions.New(store, &app.Config{AuthNURL: &authn}, 2, mainApp.Host, []string{"pwd"})
require.NoError(t, err)
token.Audience = jwt.Audience{mainApp.String()}
tokenStr, err := token.Sign(key)
Expand Down
5 changes: 4 additions & 1 deletion server/handlers/get_oauth_return.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"context"
"fmt"
"net/http"

"github.com/pkg/errors"
Expand Down Expand Up @@ -52,10 +53,12 @@ func GetOauthReturn(app *app.App, providerName string) http.HandlerFunc {
return
}

amr := []string{fmt.Sprintf("oauth:%s", providerName)}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting. i like it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I will grow to hate it - oauth, {providerName} seemed like a weird way to go since theres no real combination of methods happening.


// identityToken is not returned in this flow. it must be imported by the frontend like a SSO session.
sessionToken, _, err := services.SessionCreator(
app.AccountStore, app.RefreshTokenStore, app.KeyStore, app.Actives, app.Config, app.Reporter,
account.ID, &app.Config.ApplicationDomains[0], sessions.GetRefreshToken(r),
account.ID, &app.Config.ApplicationDomains[0], sessions.GetRefreshToken(r), amr,
)
if err != nil {
fail(errors.Wrap(err, "NewSession"))
Expand Down
6 changes: 3 additions & 3 deletions server/handlers/get_oauth_return_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestGetOauthReturn(t *testing.T) {
if !test.AssertRedirect(t, res, "https://localhost:9999/return") {
return
}
test.AssertSession(t, app.Config, res.Cookies())
test.AssertSession(t, app.Config, res.Cookies(), "oauth:test")

// creates an account
account, err := app.AccountStore.FindByOauthAccount("test", "something")
Expand All @@ -67,7 +67,7 @@ func TestGetOauthReturn(t *testing.T) {
res, err := client.WithCookie(session).Get("/oauth/test/[email protected]&state=" + state)
require.NoError(t, err)
if test.AssertRedirect(t, res, "https://localhost:9999/return") {
test.AssertSession(t, app.Config, res.Cookies())
test.AssertSession(t, app.Config, res.Cookies(), "oauth:test")
}
})

Expand All @@ -94,7 +94,7 @@ func TestGetOauthReturn(t *testing.T) {
res, err := client.Get("/oauth/test/return?code=REGISTEREDID&state=" + state)
require.NoError(t, err)
if test.AssertRedirect(t, res, "https://localhost:9999/return") {
test.AssertSession(t, app.Config, res.Cookies())
test.AssertSession(t, app.Config, res.Cookies(), "oauth:test")
}
})

Expand Down
11 changes: 7 additions & 4 deletions server/handlers/post_account.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package handlers

import (
"github.com/keratin/authn-server/lib/parse"
"net/http"

"github.com/keratin/authn-server/server/sessions"
"github.com/keratin/authn-server/lib/parse"

"github.com/keratin/authn-server/app"
"github.com/keratin/authn-server/lib/route"
"github.com/keratin/authn-server/app/services"
"github.com/keratin/authn-server/lib/route"
"github.com/keratin/authn-server/server/sessions"
)

func PostAccount(app *app.App) http.HandlerFunc {
Expand Down Expand Up @@ -36,9 +37,11 @@ func PostAccount(app *app.App) http.HandlerFunc {
panic(err)
}

amr := []string{"pwd"}

sessionToken, identityToken, err := services.SessionCreator(
app.AccountStore, app.RefreshTokenStore, app.KeyStore, app.Actives, app.Config, app.Reporter,
account.ID, route.MatchedDomain(r), sessions.GetRefreshToken(r),
account.ID, route.MatchedDomain(r), sessions.GetRefreshToken(r), amr,
)
if err != nil {
panic(err)
Expand Down
7 changes: 6 additions & 1 deletion server/handlers/post_password.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,14 @@ func PostPassword(app *app.App) http.HandlerFunc {
}
}

amr := []string{"pwd"}
if credentials.OTP != "" {
amr = append(amr, "otp")
}

sessionToken, identityToken, err := services.SessionCreator(
app.AccountStore, app.RefreshTokenStore, app.KeyStore, app.Actives, app.Config, app.Reporter,
accountID, route.MatchedDomain(r), sessions.GetRefreshToken(r),
accountID, route.MatchedDomain(r), sessions.GetRefreshToken(r), amr,
)
if err != nil {
panic(err)
Expand Down
7 changes: 6 additions & 1 deletion server/handlers/post_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,14 @@ func PostSession(app *app.App) http.HandlerFunc {
panic(err)
}

amr := []string{"pwd"}
if credentials.OTP != "" {
amr = append(amr, "otp")
}

sessionToken, identityToken, err := services.SessionCreator(
app.AccountStore, app.RefreshTokenStore, app.KeyStore, app.Actives, app.Config, app.Reporter,
account.ID, route.MatchedDomain(r), sessions.GetRefreshToken(r),
account.ID, route.MatchedDomain(r), sessions.GetRefreshToken(r), amr,
)
if err != nil {
panic(err)
Expand Down
8 changes: 4 additions & 4 deletions server/handlers/post_session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ func TestPostSessionSuccess(t *testing.T) {
require.NoError(t, err)

assert.Equal(t, http.StatusCreated, res.StatusCode)
test.AssertSession(t, app.Config, res.Cookies())
test.AssertIDTokenResponse(t, res, app.KeyStore, app.Config)
test.AssertSession(t, app.Config, res.Cookies(), "pwd")
test.AssertIDTokenResponse(t, res, app.KeyStore, app.Config, "pwd")
}

func TestPostSessionSuccessWithSession(t *testing.T) {
Expand Down Expand Up @@ -119,8 +119,8 @@ func TestPostSessionSuccessWithOTP(t *testing.T) {
require.NoError(t, err)

assert.Equal(t, http.StatusCreated, res.StatusCode)
test.AssertSession(t, app.Config, res.Cookies())
test.AssertIDTokenResponse(t, res, app.KeyStore, app.Config)
test.AssertSession(t, app.Config, res.Cookies(), "pwd", "otp")
test.AssertIDTokenResponse(t, res, app.KeyStore, app.Config, "pwd", "otp")
}

func TestPostSessionSuccessWithSessionAndTOTP(t *testing.T) {
Expand Down
7 changes: 6 additions & 1 deletion server/handlers/post_session_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,14 @@ func PostSessionToken(app *app.App) http.HandlerFunc {
panic(err)
}

amr := []string{"link"}
if credentials.OTP != "" {
amr = append(amr, "otp")
}

sessionToken, identityToken, err := services.SessionCreator(
app.AccountStore, app.RefreshTokenStore, app.KeyStore, app.Actives, app.Config, app.Reporter,
accountID, route.MatchedDomain(r), sessions.GetRefreshToken(r),
accountID, route.MatchedDomain(r), sessions.GetRefreshToken(r), amr,
)
if err != nil {
panic(err)
Expand Down
8 changes: 4 additions & 4 deletions server/handlers/post_session_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ func TestPostSessionToken(t *testing.T) {

assertSuccess := func(t *testing.T, res *http.Response, account *models.Account) {
assert.Equal(t, http.StatusCreated, res.StatusCode)
test.AssertSession(t, app.Config, res.Cookies())
test.AssertIDTokenResponse(t, res, app.KeyStore, app.Config)
test.AssertSession(t, app.Config, res.Cookies(), "link")
test.AssertIDTokenResponse(t, res, app.KeyStore, app.Config, "link")
found, err := app.AccountStore.Find(account.ID)
require.NoError(t, err)
assert.Equal(t, found.Password, account.Password)
Expand Down Expand Up @@ -124,8 +124,8 @@ func TestPostSessionTokenWithOTP(t *testing.T) {

assertSuccess := func(t *testing.T, res *http.Response, account *models.Account) {
assert.Equal(t, http.StatusCreated, res.StatusCode)
test.AssertSession(t, app.Config, res.Cookies())
test.AssertIDTokenResponse(t, res, app.KeyStore, app.Config)
test.AssertSession(t, app.Config, res.Cookies(), "link", "otp")
test.AssertIDTokenResponse(t, res, app.KeyStore, app.Config, "link", "otp")
found, err := app.AccountStore.Find(account.ID)
require.NoError(t, err)
assert.Equal(t, found.Password, account.Password)
Expand Down
15 changes: 12 additions & 3 deletions server/test/asserts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package test

import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"

jwt "gopkg.in/square/go-jose.v2/jwt"
Expand Down Expand Up @@ -35,16 +37,20 @@ func AssertErrors(t *testing.T, res *http.Response, expected services.FieldError
assert.Equal(t, string(j), string(ReadBody(res)))
}

func AssertSession(t *testing.T, cfg *app.Config, cookies []*http.Cookie) {
func AssertSession(t *testing.T, cfg *app.Config, cookies []*http.Cookie, expectedAMR ...string) {
t.Helper()
session := ReadCookie(cookies, cfg.SessionCookieName)
require.NotEmpty(t, session)

_, err := sessions.Parse(session.Value, cfg)
claims, err := sessions.Parse(session.Value, cfg)
assert.NoError(t, err)

if len(expectedAMR) > 0 {
assert.True(t, reflect.DeepEqual(claims.AuthMethodReference, expectedAMR), fmt.Sprintf("expected %v got %v", expectedAMR, claims.AuthMethodReference))
}
}

func AssertIDTokenResponse(t *testing.T, res *http.Response, keyStore data.KeyStore, cfg *app.Config) {
func AssertIDTokenResponse(t *testing.T, res *http.Response, keyStore data.KeyStore, cfg *app.Config, expectedAMR ...string) {
t.Helper()

// check that the response contains the expected json
Expand All @@ -63,6 +69,9 @@ func AssertIDTokenResponse(t *testing.T, res *http.Response, keyStore data.KeySt
if assert.NoError(t, err) {
// check that the JWT contains nice things
assert.Equal(t, cfg.AuthNURL.String(), claims.Issuer)
if len(expectedAMR) > 0 {
assert.True(t, reflect.DeepEqual(claims.AuthMethodReference, expectedAMR), fmt.Sprintf("expected %v got %v", expectedAMR, claims.AuthMethodReference))
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion server/test/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

func CreateSession(tokenStore data.RefreshTokenStore, cfg *app.Config, accountID int) *http.Cookie {
sessionToken, err := sessions.New(tokenStore, cfg, accountID, cfg.ApplicationDomains[0].String())
sessionToken, err := sessions.New(tokenStore, cfg, accountID, cfg.ApplicationDomains[0].String(), []string{"pwd"})
if err != nil {
panic(err)
}
Expand Down
Loading