Skip to content

Commit

Permalink
feat: read subject id from https://graph.microsoft.com/v1.0/me for mi…
Browse files Browse the repository at this point in the history
…crosoft (#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 #2153

Co-authored-by: splaunov <[email protected]>
  • Loading branch information
aeneasr and splaunov authored Mar 28, 2022
1 parent 794c2fd commit 852f24f
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 1 deletion.
13 changes: 13 additions & 0 deletions embedx/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions selfservice/strategy/oidc/provider_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
44 changes: 43 additions & 1 deletion selfservice/strategy/oidc/provider_microsoft.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 852f24f

Please sign in to comment.