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

GitHub OAuth Flow not working for private email addresses #3911

Open
3 of 5 tasks
jayantasamaddar opened this issue May 3, 2024 · 0 comments
Open
3 of 5 tasks

GitHub OAuth Flow not working for private email addresses #3911

jayantasamaddar opened this issue May 3, 2024 · 0 comments
Labels
bug Something is not working.

Comments

@jayantasamaddar
Copy link

jayantasamaddar commented May 3, 2024

Preflight checklist

Ory Network Project

No response

Describe the bug

Likely Bug in the GitHub OIDC Strategy preventing Private Email Addresses from being accessed

I've been trying to get the GitHub OAuth flow to work. Experimenting on my GitHub account where my email is private. Currently does not return the email address with scope set to user which supersedes user:email. Getting other information like claims.nickname, claims.name, claims.profile, claims.picture. But without the email, it's a fail.

I've done some digging and here's the code from the GitHub strategy where it clearly says:

GitHub does not provide the user's private emails in the call to /user. Therefore, if scope "user:email" is set, we want to make another request to /user/emails and merge that with our claims.

So reading the code below I see, it makes a ListEmails() call. Might want to check if this is actually working.

Check my Jsonnet below. I was able to retrieve the name from claims and parse it successfully for my Identity schema. That means, information is being retrieved. Just not the email. I've read the logs extensively using log.level set to trace

oidc.github.jsonnet:

local claims = std.extVar('claims');

{
  identity: {
    local name = std.split(claims.name, " "),
    traits: {
      // Allowing unverified email addresses enables account
      // enumeration attacks, especially if the value is used for
      // e.g. verification or as a password login identifier.
      //
      // Therefore we only return the email if it (a) exists and (b) is marked verified
      // by GitHub.
      // [if "email" in claims && claims.email_verified then "email" else null]: claims.email,
      [if "email" in claims then "email" else null]: claims.email,
      [if std.length(name) != 0 then "first_name" else null]: name[0],
      [if std.length(name) != 0 then "last_name" else null]: name[1],
    },
    metadata_public: {
      // Check if the issuer includes "github" and if preferred_username is available
      local isGitHub = if std.findSubstr("github", claims.iss) != -1 then true else false,
      github: {
        [if isGitHub then "username"]: claims.nickname,
        [if isGitHub then "profile"]: claims.profile,
        [if isGitHub then "picture"]: claims.picture,
        [if isGitHub && "email" in claims then "email" else null]: claims.email,
      },
    },
  },
}

The config file looks like this:

selfservice:
    # Browser requests, identified by the `Accept: text/html` header, complete with a redirection flow.
    # If no redirection URL is set for the flow, the Default Redirect URL will be used for most flows (for example login, registration)
    default_browser_return_url: "http://${BASE_URL}/"

    methods:
        password:
            enabled: true
        profile:
            enabled: true
        link:
            enabled: true
        oidc:
            config:
                providers:
                    - id: github # this is `<provider-id>` in the Authorization callback URL. DO NOT CHANGE IT ONCE SET!
                      provider: github
                      label: "GitHub"
                      client_id: $(GH_CLIENT_ID) # Replace this with the Client ID
                      client_secret: $(GH_CLIENT_SECRET) # Replace this with the Client secret
                      issuer_url: https://github.com # Replace this with the providers issuer URL
                      auth_url: https://github.com/login/oauth/authorize
                      token_url: https://github.com/login/oauth/access_token
                      mapper_url: "file:///etc/kratos/oidc/oidc.github.jsonnet"
                      scope:
                          - user
            enabled: true

Here's the thing with scopes for GitHub:

  • The user scope is not necessary. I have cross-verified by running this flow on Insomnia
    • https://api.github.com/user works without any scope and gives all details except private email addresses.
    • https://api.github.com/user/emails: Requires user:email. Lists email addresses.

My Hunch

  • I can't see any reason why the email shouldn't be fetched, I just did it via Insomnia. Unless the ListEmails function, isn't working as expected or there's some problem with the code from there on.

Something HERE

// GitHub does not provide the user's private emails in the call to `/user`. Therefore, if scope "user:email" is set,
	// we want to make another request to `/user/emails` and merge that with our claims.
	if stringslice.Has(grantedScopes, "user:email") {
		emails, _, err := gh.Users.ListEmails(ctx, nil)
		if err != nil {
			return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err))
		}

		for k, e := range emails {
			// If it is the primary email or it's the last email (no primary email set?), set the email.
			if e.GetPrimary() || k == len(emails) {
				claims.Email = e.GetEmail()
				claims.EmailVerified = x.ConvertibleBoolean(e.GetVerified())
				break
			}
		}
	}

Some Log Files to help

0-oidc-login-middleware.json
1-redirect-to-github-422.json
2-422-middleware.json
3-received-redirect-cb.json
4-strategy-switch.json
5-registration-strategy.json
6-json-mapped-user.json
7-mapping-wrong.json
8-302-processed.json
09-redirect-to-signup.json

Reproducing the bug

  • Turn your email address private from GitHub Email Settings
  • Start an OIDC flow with the config and JSONNET provided above.
  • Housekeeping will involve creating a button to start the flow and receiving the sign-up redirect on the registration page

Relevant log output

No response

Relevant configuration

No response

Version

v1.1.0

On which operating system are you observing this issue?

macOS

In which environment are you deploying?

Kubernetes

Additional Context

No response

@jayantasamaddar jayantasamaddar added the bug Something is not working. label May 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something is not working.
Projects
None yet
Development

No branches or pull requests

1 participant