In this guide, you will set up a hardened, fully functional OAuth 2.0 (OAuth2) server. It will take you about ~15 minutes. This guide is for you, if you are looking to do something like in the gif on the right, or more specifically:

  • You want to use OAuth2 for API security.

  • You want to open up your API to third party developers like Dropbox, or GitHub.

  • You want to become an identity provider like Google, Facebook, or Twitter.

  • You need to federate (delegate) authentication or authorization.

We will use ORY Hydra (open source), a security-first OAuth2 and OpenID Connect server written in Golang.

This is how ORY Hydra works

I originally built ORY Hydra because all other OAuth2 servers forced their user management on me. But I had a user management in place already, and did not want to migrate away from it. Some of the providers allowed me to integrate with LDAP or SAML, but this was not implemented in my user management.

ORY Hydra defines a consent flow which let's you implement the bridge between ORY Hydra and your user management easily using only a few lines of code. If you want, you could say that ORY Hydra translates the user information you provide to OAuth2 Access Tokens and OpenID Connect ID Tokens, which are then reusable across all your applications (web app, mobile app, CRM, Mail, ...) and also by third-party developers.

Sounds good, let's go!

You're hooked? Great! Let's start by downloading some docker images. By the way, this tutorial relies heavily on docker. If you haven't already, please install docker now.

my-anchor
grey

Install and run PostgreSQL

First, a database is required, and we will choose PostgreSQL 9.6 (MySQL is also supported).

This docker command starts postgres container ory-hydra-example--postgres and sets up a database called hydra with user hydra and password secret.

$ docker run \
--name ory-postgres \
-e POSTGRES_USER=hydra \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=hydra \
-d postgres:9.6

By the way, don't deploy databases using docker in production. It will make your life miserable. Use a managed solution like Amazon RDS or Google Cloud SQL. Even small instances will be able to serve a lot of traffic, check out some of the benchmarks.

my-anchor
white

Configure the environment

$ export SYSTEM_SECRET=y82XL-wAPCCZu+B4

System Secret

The system secret is used to encrypt data at rest, and to sign tokens and authorize codes. Once a database is initialized with a system secret, you always need to use that secret to access the database.

$ export DATABASE_URL=postgres://hydra:[email protected]:5432/hydra?sslmode=disable

Database

The database url must point to the postgres container we created above. The database will be used to persist and query data. ORY Hydra prevents data leaks as only token signatures are stored in the database. For a valid token, both payload and signature are required.

$ docker pull oryd/hydra:v0.9.12
$ docker run \
--link ory-postgres:postgres \
-it --entrypoint \
hydra oryd/hydra:latest \
migrate sql $DATABASE_URL

Initialize the database

Next, the database needs to be initialized. This can be achieved with hydra migrate sql. Here we pull the latest docker image for ORY Hydra and run a container that executes the migrate command.

To prevent bad things from happening, SQL migrations are never run without you explicitly telling them to. This is the case for new and existing databases.

my-anchor

Run the OAuth2 server

Besides setting the system secret (SYSTEM_SECRET), the database url (DATABASE_URL), the public url (ISSUER) of the server and the root client credentials (FORCE_ROOT_CLIENT_CREDENTIALS) used to manage ORY Hydra, a consent url (CONSENT_URL) is passed using environment variables.

The consent url points to a web service which will be explained and set up in the next sections. It connects Hydra to your identity management and is part of the consent flow.

Please use more secure values in production. ;)

$ docker run -d \
--name ory-hydra \
--link ory-postgres:postgres \
-p 9000:4444 \
-e SYSTEM_SECRET=$SYSTEM_SECRET \
-e DATABASE_URL=$DATABASE_URL \
-e ISSUER=https://localhost:9000/ \
-e CONSENT_URL=http://localhost:9020/consent \
-e FORCE_ROOT_CLIENT_CREDENTIALS=admin:demo-password \
oryd/hydra:v0.9.12

Is it alive?

This is easy to answer, just check the docker logs! Or, open the health check, which should show you ok.

If asked, accept the self signed certificate in your browser.

$ docker logs ory-hydra

[...]
time="2017-06-29T21:26:34Z" level=info msg="Setting up http server on :4444"
my-anchor
white

Generate and validate tokens

$ docker run -p 9010:4445 \
--link ory-hydra:hydra \
-it --entrypoint "/bin/sh" \
oryd/hydra:v0.9.12

SSH into bash container with Hydra

In this section, you will connect to the Hydra instance and generate and validate access tokens for the root client. To save you the trouble of installing Hydra locally, SSH into a container running bash with the ORY Hydra command line interface (CLI) installed.


$ hydra connect


Cluster URL []: https://hydra:4444
Client ID []: admin
Client Secret [empty]: demo-password

Connect with Hydra

The command line interface (CLI) needs to know where Hydra is and what client id and secret should be used. Since we used FORCE_ROOT_CLIENT_CREDENTIALS=admin:demo-password when creating the container, we know that the combination is admin:demo-password.

$ hydra token client --skip-tls-verify

tY9tGakiYAUn8VIGn_yCDlTahckSfGbDQIlXahjXtX0.BQlCxRDL3ngag6hdsSl9N2qrz7R399cQMfld8aI2Mlg

Issue an access token

Using hydra token client it is possible to obtain an access token for the client we used during hydra connect, in our case that's admin.

Because only a self-signed TLS (for HTTPS) certificate is available, all interactions must skip TLS certificate verification which can be achieved with the flag --skip-tls-verify.

$ hydra token validate \
--skip-tls-verify \
$(hydra token client --skip-tls-verify)

{
"active": true,
"sub": "admin",
[...]

Validate an access token

Using hydra token validate it is possible to validate an access token, and receive it's payload. ORY Hydra uses opaque tokens to greatly reduce attack vectors. The payload of the token can be modified using the consent response.

You can validate access tokens using the Introspection API, standardized as IETF OAuth 2.0 Token Introspection.

my-anchor

The consent flow

ORY Hydra is not an Identity Management solution. Instead it uses your existing Identity Management which reduces adoption complexity. OAuth2 providers such as Keycloak, OpenAM, or IdentityServer are usually full-stack enterprise identity and access management solutions. They come with complex deployment dependencies, technologies not particularly suited for cloud native environments, and subtle, but annoying limitations at scale. ORY Hydra solves OAuth2 and OpenID Connect only, but it solves it well and extremely scalable.

To authenticate users, ORY Hydra defines the consent flow. Example consent apps are available for Go and Node as well as Consent SDKs for both languages.

Create a consent client

The consent app requires a registered OAuth 2.0 Client with the ability to access (scope) cryptographic keys (hydra.keys.get) through OAuth2 Access Tokens. The keys are required for validating and signing the consent challenge and the consent response.

$ hydra clients create --skip-tls-verify \
--id consent-app \
--secret consent-secret \
--name "Consent App Client" \
--grant-types client_credentials \
--response-types token \
--allowed-scopes hydra.keys.get

Create access control policies for consent client

ORY Hydra uses access control policies internally. This functionality is exposed as the Warden API. Since the consent app requires access to cryptographic keys, an access control policy for consent-app has to be created as well. This is different from an OAuth2 scope as it regulates the capabilities of the user (consent-app), not the scope of the access token.

Read more about access control policies.

$ hydra policies create --skip-tls-verify \
--actions get \
--allow \
--id consent-app-policy \
--resources "rn:hydra:keys:hydra.consent.<.*>" \
--subjects consent-app

Run the consent app container

Since there is an exemplary consent app for NodeJS available, let's use that one in docker and connect it to ORY Hydra. You need to run this command in a different shell. After running this command, return to the shell above for the remainder of this article.

$ docker run -d \
--name ory-consent \
--link ory-hydra:hydra \
-p 9020:3000 \
-e HYDRA_CLIENT_ID=consent-app \
-e HYDRA_CLIENT_SECRET=consent-secret \
-e HYDRA_URL=https://hydra:4444 \
-e NODE_TLS_REJECT_UNAUTHORIZED=0 \
oryd/hydra-consent-app-express:latest
my-anchor
white

OAuth2 + OpenID Connect Authorize Flow

Create OAuth2 Consumer App

Awesome, the infrastructure is now set up! To perform the OAuth 2.0 and OpenID Connect flow, an OAuth 2.0 Client (consumer app) is required.

The client must be able to request the authorize_code grant, scopes openid and offline, and response types token, code, and id_token.

Allowing the hydra.clients.get let's us request tokens capable of querying OAuth 2.0 Clients stored in ORY Hydra. This is just for demonstration purposes.

$ hydra clients create --skip-tls-verify \
--id some-consumer \
--secret consumer-secret \
-g authorization_code,refresh_token,client_credentials \
-r token,code,id_token \
--allowed-scopes openid,offline,hydra.clients \
--callbacks http://localhost:9010/callback

Client ID: some-consumer
Client Secret: consumer-secret

Perform OAuth 2.0 Authorize Code Flow

To initialize an OAuth 2.0 authorize code flow, use the hydra token user command. It will generate the authorization url which the user must open in the browser. Requesting the authorization is the first step of the OAuth 2.0 authorize code flow.

We also request that the token allows us to manage and fetch OAuth 2.0 Clients from the database using the scope hydra.clients. We will use the token later to fetch those clients in an example.

Requesting OAuth 2.0 Access and Refresh tokens is usually done using a library for your programming language. Do not write this on your own. Here are some libraries for different languages: Golang, NodeJS, PHP.

$ hydra token user --skip-tls-verify \
--auth-url https://localhost:9000/oauth2/auth \
--token-url https://hydra:4444/oauth2/token \
--id some-consumer \
--secret consumer-secret \
--scopes openid,offline,hydra.clients \
--redirect http://localhost:9010/callback

Setting up callback listener on http://localhost:4445/callback
Press ctrl + c on Linux / Windows or cmd + c on OSX to end the process.
If your browser does not open automatically, navigate to:

https://localhost:9000/oauth2/auth?client_id=some-consumer&redirect_uri=http%3A%2F%2Flocalhost%3A9020%2Fcallback&response_type=code&scope=openid+offline+hydra.clients&state=hfcyxoqoctwbnvrxrsuwgzfu&nonce=lbeouolavuvcdhjefcnzlqur

Login & Consent

Next, you will be shown two screens. One is the log-in screen, and the other is the screen asking the users which permissions he want's to grant. These two screens are fully in your control and you can do anything you want here. Just remember the security implications of, for example, automatically granting everyone admin rights. ;)

In the consent screen, make sure to tick all the boxes! You can try how the system works when not checking all of them later.

Access Token, Refresh Token & ID Token!

Once you logged in and granted some scopes, ORY Hydra will issue an access, a refresh (if scope offline was granted), and an id token (if scope openid was granted).

my-anchor

Make an authorized request

The last part of this tutorial is going to teach you how to make an authorized request. For demonstration purposes, we will use an ORY Hydra API. It is however no different from how requests would look like to your API.

Run these commands in a new shell where you have access to curl. Do not close the ORY Hydra shell, we will need it soon.

To make an authorized request, copy the access token you received above and set it as the environment variable ACCESS_TOKEN.

But we are seeing an error message telling us that this request is not allowed. We just encountered access control policies in action.

$ export ACCESS_TOKEN=<paste token from browser>
$ export ACCESS_TOKEN=fUAvz53...

$ curl \
--insecure \
-H "Authorization: bearer $ACCESS_TOKEN" \
https://127.0.0.1:9000/clients/some-consumer

{"error":{"code":500,"message":"Request was denied by default: The request is not allowed"}}

While the token is allowed to potentially access this API, the user itself is not! In order to be able to access this API, the user needs to have the permission to access it. The OAuth 2.0 scope is only representative for the Access Token, not the user!

Let's allow this user access to the clients API by running hydra policies create in the ORY Hydra shell from the previous sections. The user id is set in the exemplary consent app we used and is user:12345:dandean.

 hydra policies create --skip-tls-verify \
--actions get \
--allow \
--id consent-app-policy \
--resources "rn:hydra:clients:<.*>" \
--subjects user:12345:dandean

Now, re-running the curl command gives the expected result!

$ curl --insecure \
-H "Authorization: bearer $ACCESS_TOKEN"
https://127.0.0.1:9000/clients/some-consumer

{
"id":"some-consumer",
"client_name":"",
"redirect_uris":["http://localhost:9010/callback"],
[...]
}

That's it, you have a running OAuth2 server with an exemplary identity provider, and performed an OAuth2 request!

ORY Hydra is an Apache 2.0 licensed Go server solving OAuth 2.0, OpenID Connect and API security in general. It secures several production systems around the world and has a vibrant and welcoming online community.

my-anchor
white

Check out ORY Hydra at Github and our
API Security products.

my-anchor
grey