From 852f24fb5cd8576f3f6d35017ce85e4fa1c51c95 Mon Sep 17 00:00:00 2001 From: hackerman <3372410+aeneasr@users.noreply.github.com> Date: Mon, 28 Mar 2022 15:25:28 +0200 Subject: [PATCH] feat: read subject id from https://graph.microsoft.com/v1.0/me for microsoft (#2347) Adds the ability to read the OIDC subject ID from the `https://graph.microsoft.com/v1.0/me` endpoint. This introduces a new field `subject_source` to the OIDC configuration. Closes https://github.com/ory/kratos/pull/2153 Co-authored-by: splaunov --- embedx/config.schema.json | 13 ++++++ selfservice/strategy/oidc/provider_config.go | 7 +++ .../strategy/oidc/provider_microsoft.go | 44 ++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/embedx/config.schema.json b/embedx/config.schema.json index 9936c29b8a30..c7198b52f0db 100644 --- a/embedx/config.schema.json +++ b/embedx/config.schema.json @@ -409,6 +409,19 @@ "contoso.onmicrosoft.com" ] }, + "subject_source": { + "title": "Microsoft subject source", + "description": "Controls which source the subject identifier is taken from by microsoft provider. If set to `userinfo` (the default) then the identifier is taken from the `sub` field of OIDC ID token or data received from `/userinfo` standard OIDC endpoint. If set to `me` then the `id` field of data structure received from `https://graph.microsoft.com/v1.0/me` is taken as an identifier.", + "type": "string", + "enum": [ + "userinfo", + "me" + ], + "default": "userinfo", + "examples": [ + "userinfo" + ] + }, "apple_team_id": { "title": "Apple Developer Team ID", "description": "Apple Developer Team ID needed for generating a JWT token for client secret", diff --git a/selfservice/strategy/oidc/provider_config.go b/selfservice/strategy/oidc/provider_config.go index 15b003221683..f0f48660e76e 100644 --- a/selfservice/strategy/oidc/provider_config.go +++ b/selfservice/strategy/oidc/provider_config.go @@ -59,6 +59,13 @@ type Configuration struct { // `8eaef023-2b34-4da1-9baa-8bc8c9d6a490` or `contoso.onmicrosoft.com`. Tenant string `json:"microsoft_tenant"` + // SubjectSource is a flag which controls from which endpoint the subject identifier is taken by microsoft provider. + // Can be either `userinfo` or `me`. + // If the value is `uerinfo` then the subject identifier is taken from sub field of uderifo standard endpoint response. + // If the value is `me` then the `id` field of https://graph.microsoft.com/v1.0/me response is taken as subject. + // The default is `userinfo`. + SubjectSource string `json:"subject_source"` + // TeamId is the Apple Developer Team ID that's needed for the `apple` `provider` to work. // It can be found Apple Developer website and combined with `apple_private_key` and `apple_private_key_id` // is used to generate `client_secret` diff --git a/selfservice/strategy/oidc/provider_microsoft.go b/selfservice/strategy/oidc/provider_microsoft.go index 9c030fba6bac..e375180a3402 100644 --- a/selfservice/strategy/oidc/provider_microsoft.go +++ b/selfservice/strategy/oidc/provider_microsoft.go @@ -2,8 +2,13 @@ package oidc import ( "context" + "encoding/json" "strings" + "github.com/hashicorp/go-retryablehttp" + + "github.com/ory/x/httpx" + "github.com/gofrs/uuid" "github.com/golang-jwt/jwt/v4" @@ -66,7 +71,44 @@ func (m *ProviderMicrosoft) Claims(ctx context.Context, exchange *oauth2.Token) return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to initialize OpenID Connect Provider: %s", err)) } - return m.verifyAndDecodeClaimsWithProvider(ctx, p, raw) + claims, err := m.verifyAndDecodeClaimsWithProvider(ctx, p, raw) + if err != nil { + return nil, err + } + + return m.updateSubject(ctx, claims, exchange) +} + +func (m *ProviderMicrosoft) updateSubject(ctx context.Context, claims *Claims, exchange *oauth2.Token) (*Claims, error) { + if m.config.SubjectSource == "me" { + o, err := m.OAuth2(ctx) + if err != nil { + return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) + } + + client := m.reg.HTTPClient(ctx, httpx.ResilientClientWithClient(o.Client(ctx, exchange))) + req, err := retryablehttp.NewRequest("GET", "https://graph.microsoft.com/v1.0/me", nil) + if err != nil { + return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) + } + + resp, err := client.Do(req) + if err != nil { + return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to fetch from `https://graph.microsoft.com/v1.0/me`: %s", err)) + } + defer resp.Body.Close() + + var user struct { + ID string `json:"id"` + } + if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { + return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Unable to decode JSON from `https://graph.microsoft.com/v1.0/me`: %s", err)) + } + + claims.Subject = user.ID + } + + return claims, nil } type microsoftUnverifiedClaims struct {