diff --git a/.schema/config.schema.json b/.schema/config.schema.json index 51f5fb0b7ab..7850df405cf 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -143,10 +143,11 @@ }, "provider": { "title": "Provider", - "description": "Can be one of github, generic, google, microsoft.", + "description": "Can be one of github, gitlab, generic, google, microsoft.", "type": "string", "enum": [ "github", + "gitlab", "generic", "google", "microsoft" diff --git a/docs/docs/guides/sign-in-with-github-google-facebook-linkedin.mdx b/docs/docs/guides/sign-in-with-github-google-facebook-linkedin.mdx index 4583af2c2fa..3972e9b05fa 100644 --- a/docs/docs/guides/sign-in-with-github-google-facebook-linkedin.mdx +++ b/docs/docs/guides/sign-in-with-github-google-facebook-linkedin.mdx @@ -1,6 +1,6 @@ --- id: sign-in-with-github-google-facebook-linkedin -title: Sign in with GitHub, Google, Facebook, LinkedIn, Microsoft ... +title: Sign in with GitHub, GitLab, Google, Facebook, LinkedIn, Microsoft ... --- In this document we will take a look at setting up "Sign in with GitHub" using @@ -126,6 +126,73 @@ selfservice: Next, open the login endpoint of the SecureApp and you should see the GitHub Login option! +## GitLab + +To set up "Sign in with GitLab" you must create a [GitLab OAuth2 Application](https://docs.gitlab.com/ee/integration/oauth_provider.html#adding-an-application-through-the-profile). + +Set the "Redirect URI" to: + +``` +http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/strategies/oidc/callback/gitlab +``` + +The pattern of this URL is: + +``` +http(s)://:/self-service/browser/flows/strategies/oidc/callback/ +``` + +:::note +While you can use [GitLab as an OIDC identity provider](https://docs.gitlab.com/ee/integration/openid_connect_provider.html), +GitLab only returns the sub and sub_legacy claims in the ID token. +Therefore, ORY Kratos makes a request to [GitLab's /oauth/userinfo API](https://gitlab.com/oauth/userinfo) and adds the user info to `std.extVar('claims')`. +::: + +```json title="contrib/quickstart/kratos/email-password/oidc.gitlab.jsonnet" +local claims = { + email_verified: false +} + std.extVar('claims'); + +{ + identity: { + 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 GitLab. + [if "email" in claims && claims.email_verified then "email" else null]: claims.email, + }, + }, +} +``` + +Now, enable the GitLab provider in the ORY Kratos config located at +`/contrib/quickstart/kratos/email-password/.kratos.yml`. + +```yaml title="contrib/quickstart/kratos/email-password/.kratos.yml" +# $ kratos -c path/to/my/kratos/config.yml serve +selfservice: + strategies: + oidc: + enabled: true + config: + providers: + - id: gitlab # this is `` in the Authorization callback URL. DO NOT CHANGE IT ONCE SET! + provider: gitlab + client_id: .... # Replace this with the OAuth2 Client ID provided by GitLab + client_secret: .... # Replace this with the OAuth2 Client Secret provided by GitLab + mapper_url: file:///etc/config/kratos/oidc.gitlab.jsonnet + scope: + - read_user + - openid + - profile + - email +``` + +GitLab is now an option to log in via Kratos. + ## Microsoft This will enable you to log in using any Azure AD directory - Multitenant and diff --git a/docs/docs/self-service/flows/user-login.mdx b/docs/docs/self-service/flows/user-login.mdx index 3debf37da68..62b13af0173 100644 --- a/docs/docs/self-service/flows/user-login.mdx +++ b/docs/docs/self-service/flows/user-login.mdx @@ -193,6 +193,7 @@ The Social Sign In Method (`oidc`) enables you to use - [GitHub](http://github.com/); - [Apple](https://developer.apple.com/sign-in-with-apple/); +- [GitLab](https://docs.gitlab.com/ee/integration/openid_connect_provider.html); - [Google](https://developers.google.com/identity/sign-in/web/sign-in); - [Facebook](https://developers.facebook.com/docs/facebook-login/); - [ORY Hydra](https://www.ory.sh/hydra); diff --git a/docs/docs/self-service/flows/user-registration.mdx b/docs/docs/self-service/flows/user-registration.mdx index 54d6978d8ed..a57db4f7ca0 100644 --- a/docs/docs/self-service/flows/user-registration.mdx +++ b/docs/docs/self-service/flows/user-registration.mdx @@ -226,6 +226,7 @@ The Social Sign Up Method (`oidc`) enables you to use - [GitHub](http://github.com/); - [Apple](https://developer.apple.com/sign-in-with-apple/); +- [GitLab](https://docs.gitlab.com/ee/integration/openid_connect_provider.html); - [Google](https://developers.google.com/identity/sign-in/web/sign-in); - [Facebook](https://developers.facebook.com/docs/facebook-login/); - [ORY Hydra](https://www.ory.sh/hydra); diff --git a/selfservice/strategy/oidc/provider_config.go b/selfservice/strategy/oidc/provider_config.go index 3902a65897d..48fea5b2607 100644 --- a/selfservice/strategy/oidc/provider_config.go +++ b/selfservice/strategy/oidc/provider_config.go @@ -83,6 +83,8 @@ func (c ConfigurationCollection) Provider(id string, public *url.URL) (Provider, return NewProviderGoogle(&p, public), nil case "github": return NewProviderGitHub(&p, public), nil + case "gitlab": + return NewProviderGitLab(&p, public), nil case "microsoft": return NewProviderMicrosoft(&p, public), nil } diff --git a/selfservice/strategy/oidc/provider_gitlab.go b/selfservice/strategy/oidc/provider_gitlab.go new file mode 100644 index 00000000000..af9aade4c2b --- /dev/null +++ b/selfservice/strategy/oidc/provider_gitlab.go @@ -0,0 +1,73 @@ +package oidc + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + "path" + + "github.com/ory/herodot" + "github.com/pkg/errors" + "golang.org/x/oauth2" +) + +const ( + defaultEndpoint = "https://gitlab.com" +) + +type ProviderGitLab struct { + *ProviderGenericOIDC +} + +func NewProviderGitLab( + config *Configuration, + public *url.URL, +) *ProviderGitLab { + return &ProviderGitLab{ + ProviderGenericOIDC: &ProviderGenericOIDC{ + config: config, + public: public, + }, + } +} + +func (g *ProviderGitLab) Claims(ctx context.Context, exchange *oauth2.Token) (*Claims, error) { + o, err := g.OAuth2(ctx) + if err != nil { + return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) + } + + client := o.Client(ctx, exchange) + + u, err := g.endpoint() + if err != nil { + return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) + } + u.Path = path.Join(u.Path, "/oauth/userinfo") + req, err := http.NewRequest("GET", u.String(), 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("%s", err)) + } + defer resp.Body.Close() + + var claims Claims + if err := json.NewDecoder(resp.Body).Decode(&claims); err != nil { + return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) + } + + return &claims, nil +} + +func (g *ProviderGitLab) endpoint() (*url.URL, error) { + var e = defaultEndpoint + if len(g.config.IssuerURL) > 0 { + e = g.config.IssuerURL + } + return url.Parse(e) +}