Gitlab has several OAuth2 related features. The relevant here is the possibility to sign into GitLab with (almost) any OAuth2 provider, in this case ORY Hydra. So, in this guide, we'll connect GitLab's omniauth-connector to ORY Hydra. We'll do that in a docker-based lab-environment in order to investigate the details before you do something like this in production.
Even though we're mostly using ORY Hydra in a docker-container, having the command-line-client available is quite useful. So please install ORY Hydra as explained in the installation-guide. You'll also need docker and docker-compose.
The 5-min-tutorial might be worth checking out upfront. It'll a give a nice quick overview how OAuth2 is working within ORY Hydra with a minimal example. We assume basic knowledge, here.
If you have not yet the source code of ORY Hydra, which we'll need for the
docker-compose yaml-files and the gitlab-configuration, clone the repository:
We will access GitLab via the url http://gitlab.example.com. So we need to map
it to localhost. This is done by modifying the hosts-file. On an unixoid system
find this file in
/etc/hosts, on windows, you should find it in
c:\WINDOWS\system32\drivers\etc\hosts. Add this line:
As this POC will work with http instead of https, we need to whitelist the above domain-name to allow unencrypted http traffic. So add the following switch to the services.hydra.command-section in the quickstart.yml around line 24 so that the line looks like this:
Spin up the instances and logging in
Use this command to spinup the instances. This will show the logs on the terminal and it will take some time.
After this suceeds, you can access the login page sign-in-page. Don't try to login, yet, we have to create the client in ORY Hydra, first.
Creating the client in ORY Hydra
Depending on whether you've the hydra-binary available, you can use it directly or the one in the docker-container.
or you can use the binary within the docker-container:
With the first access of your GitLab-instance, you will have to change the root-password. You should see a "Ory Hydra" Login-button. Clicking it will forward you to the hydra-consent-app where you can login with firstname.lastname@example.org/foobar similiar to the 5-min-tutorial. After that you have to give consent to accessing your email-address. Congratulations, doing that should redirect you directly to your personal GitLab-page. You have logged into GitLab via ORY Hydra.
So now, let's look at the individual pieces and how all of them work together.
Gitlab has some documentation about
how to use their docker-images. It has also an example for docker-compose. The
quickstart-gitlab.yaml file in the contrib directory doesn't contain
data, the config directory is already prepopulated and
the single most important configuration-file is the
gitlab.rb file. GitLab has
a mechanism to override values and we use it here to specify the
So let's move on to
GitLab configuration - OAuth2-setup
The gitLab-configuration in
contrib/quickstart/gitlab/config/gitlab.rb is the
original "template" which consists of 2400 lines of comments on how to do stuff.
Our relevant configuration starts at line 432 where the corresponding comments
about OAuth2 is located as well. It looks like this:
The documentation for this, other then inside the file, is a bit scattered:
- A specific step-by-step guide but not very detailed
- OmniAuth reference documentation in GitLab
- OmniAuth Generic reference documenation in GitLab
- OmniAuth Gem-documentation from Satorix
The biggest-source for errors is the clients-options-section. Here we'll specify the details for the OAuth2 flow and where ORY Hydra is located. Two things are extremely important to keep in your mind when looking at configurations which are specifying some flow one way or another:
- Where is the DNS-name resolved? Sometimes it's on the users browser, sometimes on GitLab or on the hydra-side. Especially in our docker-based POC, it makes a huge difference!
- Cookies can only be written/read, if they are from the same domain. In that case "127.0.0.1". That would be a different domain than "localhost". Pay attention to that.
These two points in our mind, let's look at the three configurations:
'site' => 'http://127.0.0.1:4444'This is the default for the three URLs later if not specified otherwise.
'authorize_url' => 'http://127.0.0.1:4444/oauth2/auth'this url will be a redirect-target and therefore resolved on the browser of the user. Probably we could omit the scheme, host and port as this is already defined in
'token_url' => 'http://hydra:4444/oauth2/token'the
token_urlwill get used on the GitLab-server to get a token after GitLab received the grant. As it's resolved on the GitLab-side, we're using docker-name of the hydra-container which is by default resolvable on the GitLab-container.
'user_info_url' => 'http://hydra:4444/userinfo',same thing for the
user_info_url. It's called on the GitLab-container and needs to be resolvable there.
The paths here are by default the same paths which are specified by OpenID connect. The configuration would be simpler if we would use OpenID-Connect (more about that later in the appendix) but in our case we're simply manually specifying the values. So it's not an accident that these pathes here are the very same then what you get from ORY Hydra:
Also worth noting here is the supported
token_endpoint_auth_methods: How does
GitLab authenticate against ORY Hydra? So GitLab is using
which we needed to specify when we've created the GitLab-client in ORY Hydra.
Some remarks for creating the client. We have created the client like this. The second command shows the created client:
- The endpoint is not part of the configuration but it's a command-line-switch telling the hydra-binary to which hydra-instance to talk to
secrethas been specified before in the GitLab-config
- the token-endpoint-auth-method is by default
client_secret_basicbut GitLab is using
client_secret_post(couldn't find that anywhere in the GitLab-documentation, though)
- The callback needs to be resolvable on the users-browser. However, originally,
the callback-url is created on the GitLab-side. In order to make that
resolvable on the client, we set the
external_urlin the GitLab-configuration. Here that value is just there to cross-check with the generated one. It needs to match exactly.
Proper GitLab user-creation
Initially, GitLab doesn't have any user but it needs them in order to manage authorisation, no matter how the login is done. This is a common issue and a common solution to this is to create the users on the fly with the first login. So in order to do that, these lines are enabling that:
In order to get the necessary data for the user, gitlab needs to call to hydra's userinfo-endpoint. The most important attribute is the sub-attribute which provides, according to the specification, the ID of a user which is (in this case) the email-address. However, the email-address is also an attribute in the specification but in the implementation of the of this one hardcoded user (email@example.com) is empty.
So therefore we're specifying a mapping telling gitlab it should take the sub-field and use it as email:
Whether the attribute "email" is there or not is quite critical here. The Login-ID has the form of an email. So in order to satisfy Gitlab's requirement, we're mapping here the email-attribute to the Login-ID which is represented by "sub". This shouldn't be necessary in a real-world-implementation.
But assuming that it's not doing that mapping, then GitLab would need to ask ORY Hydra on that endpoint the email-address. But is GitLab even allowed to read it? We need consent from the user for that and fortunately we configured the client above to be able to ask for that scope. However, we also need to configure GitLab to actually ask for that scope:
We've successfully integrated GitLab with ORY Hydra. Everything was done as configuration. No code has been created nor has any application been monkey-patched while following this guide (so far).
After trying to login you get a message like this:
Error: invalid_client Description: Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method) Hint: The requested OAuth 2.0 Client does not exist.
Doublecheck your registered clients. Mae sure ID and password are correct and matches that of the gitlab.rb:
From Hydra: request is missing ... or otherwise malformed
So after this, clicking the login-button on the sign-in-page will forward to Ory Hydra, which will redirect to the consent-app on port 3000. After the login, you'd get to the granting-page of the consent-app and after you've "allowed access", you'll get redirected back to gitlab which will unfortunately mention:
Could not authenticate you from ORYHydra because "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed".
So the message in quotes is from ORY Hydra and not very expressive. Note that it's a bit difficult to expose very meaningful error-messages as this could be used for security-attacks. So in such cases check the hydra-logs on what's wrong.
ORY Hydra Logs: Redirect URL is using an insecure protocol
The relevant part here is:
Redirect URL is using an insecure protocol. Make
sure to add the --dangerous-force-http to the hydra-command as described above.
After that, restart the hydra-container like this:
GitLab: Signing in ... is not allowed
Signing in using your Ory Hydra account without a pre-existing GitLab account is not allowed. Create a GitLab account first, and then connect it to your Ory Hydra account.
Double-Check the above explanation about user-creation.
GitLab: Email can't be blank
Sign-in failed because Email can't be blank and Notification email can't be blank.
user_response_structure and the
attributes need an email-entry.
Appendix: Some notes about OpenID connect
GitLab is supporting OpenID-Connect and ORY Hydra does that as well. Why hasn't that been used in this guide?
OpenID might be the better choice then plain OAuth2. When we tried that, we ran into the issue that the used OpenID-connect-implementation does not allow http-connection but "only" https. That's in general a good thing but stupid for POCs like this. Whereas ORY Hydra has a switch to whitelist URLs in such cases, the used openid-connect-implementation doesn't seem to have that. So, here is a reasonable openID-Connect configuration:
In order to make that work which is not ssl, we need to patch the openid_connect gem. Checkout the details here.
In order to avoid that scenario, this guide avoids OpenID-Connect. There might also be other issues with OpenID-Connect on GitLab.