Practical Example of Implementing OAuth 2.0 Using ory/hydra

  • The authentication and authorization process can be done in one handler/API, makes it easy to understand and implement.
  • There is no separation of concern between authorization and authentication process.
  • Your user service may be blocked by tremendous data of access tokens.
  • Hard to maintain multi-application with same user’s credential. For example, service Gmail may consist of n services behind it and Youtube m services, but if we only build Auth service for Gmail only, it sometimes hard to Youtube to use the same login credential.

OAuth 2.0

Authentication VS Authorization

  • Third Party Application (Client): or OAuth 2.0 Client is the external application (can be mobile app or web app or any application) that will access Protected Resource if Resource Owner (User) gives the access. To know that, the Client will ask Authorization Server.
  • Resource Owner (User): is a end-user who have username and password (or other credentials) to access Protected Resource.
  • Authorization Server: is a server to proof that this user is authorized or permitted to access the Protected Resource. In the last section we will use ory/hydra as Authorization Server.
  • Resource Server (Authentication Server): or others called it as an Identity Provider, is a server to proof that this user has the right credentials to do the operation in the Protected Resource. It commonly a username and password.
  • Protected Resource: or sometimes called as Resource Provider, is a server(s) where the data can only be accessed if user is authenticated AND has permission (authorized) to access that data.

Grant Type: A Method To Authorize Your Client

Figure 1. Authorization Code Flow. Image source: https://darutk.medium.com/diagrams-and-movies-of-all-the-oauth-2-0-flows-194f3c3ade85 accessed at 6 February 2021 18.39 GMT+7
  • client_id: A Client ID that the Third Party Application (Client) owner obtained from the OAuth 2.0 service. This includes client_secret.
  • redirect_uri: a Front-End or website page or URI where the access token (and sometimes refresh token) will given back. This URI must be registered first when creating an OAuth 2.0 Client. Usually it will be redirected to the Third Party Application (Client) page.
  • response_type: for Authorization Code grant, use “code”.
  • scope: list of permission that client (or the 3rd party) asks to access protected resource on user’s behalf.
  • state: a random code to be validated later when access_token is given back to the application. This is used to prevent CSRF attacks.
  • Resource Server will validates the username and password.
  • Once Resource Server say “okay”, then Resource Server may request to Authorization Server to ask Resource Owner (User)’s consent about what permissions he/she give to Third Party Application (Client) to access. This process done by sending “scope” parameters to the Authorization Server.
  • When authentication and permission process is done, Resource Server then ask Authorization Server to issue short-live authorization code. This code will be used later by the Third Party Application (Client) to be exchanged with Access Token to access Protected Resource.

Implementing OAuth 2.0 using ory/hydra

Figure 2. OAuth 2.0 Flow using Ory/Hydra. Image source: https://www.ory.sh/hydra/docs/concepts/login accessed 7 February 2021 21.13 GMT+7
  1. Login Page — a Web UI that show a form for user to input their credentials. This is needed in process “Redirects end user with login challenge” and to “Fetches login info”. In this page, we need to capture a login_challenge from query parameter that sent by Hydra when “OAuth 2.0 Client initiates OAuth2 Authorize Code or Implicit Flow.”
    We then need to call Hydra to check whether current user already logged in (checked via cookie or remember flag). If already logged in (response key skip is true) then we do not need to show this login page, instead we use previous login info data to accept the login request and “Redirects end user to redirect url with login verifier”. Otherwise, we need to show a login form.
  2. Login Handler — an API to handle user’s info login. This handler will retrieve username and password of the User, then it will validate whether the inputted credentials is true. When valid, we need to accepts the login request to Hydra using login_challenge code sent by Hydra in process “Redirects end user with login challenge”. After accepting the request, Hydra will generate a consent_challenge and “Redirects end user with consent challenge” to Consent Page.
  3. Consent Page — a Web UI shows a list of permission that Resource Owner (User) need to accepts. For example, if the app need to access User’s profile info, it probably show a checkbox to access user info where the User can unchecked it (disagree) or checked it (agree). In this page, we will receive a consent_challenge that we need to send back to Hydra with list of scopes (permissions) that user agreed.
    We need to ask Hydra whether current request has requested the same scopes from the same user previously. If yes then skip parameter is true and you must not ask the user to grant the requested scopes.
  4. Consent Handler — an API handler to receive action of Consent Page (process 3). This handler will retrieve a grant_scope that User agreed in Consent Page UI, then ask Hydra to accepts Consent Request. The final response of this proccess is a redirect URI where Hydra will “Redirects to redirect url with consent verifier”. Hydra then verifies grant and transmits authorization code/token back to the redirect_uri in Third Party Application (Client). The Client then needs to exchange this Authorization code with an access token.
Figure 3. Sample of Login Page in “Sign In With Google” button. Image source: https://developers.google.com/identity/sign-in/web/sign-in accessed at 8 February 2021 09:04 GMT+7
Figure 4. Consent Page in “Sign In With Google”. Image source: https://stackoverflow.com/questions/60954001/how-can-i-configure-google-oauth-consent-screen-to-not-show-checkboxes accessed at 8 February 2021 09:08 GMT+7
  1. GET /authentication/login — https://github.com/yusufsyaifudin/oauth2-example-hydra/blob/v0.1.0/cmd/authc/handler/login_get.go
  2. POST /authentication/login — https://github.com/yusufsyaifudin/oauth2-example-hydra/blob/v0.1.0/cmd/authc/handler/login_post.go
  3. GET /authentication/consent — https://github.com/yusufsyaifudin/oauth2-example-hydra/blob/v0.1.0/cmd/authc/handler/consent_get.go
  4. POST /authentication/consent — https://github.com/yusufsyaifudin/oauth2-example-hydra/blob/v0.1.0/cmd/authc/handler/consent_post.go
URLS_LOGIN=http://localhost:8000/authentication/login
URLS_CONSENT=http://localhost:8000/authentication/consent
docker-compose -f quickstart.yml exec hydra \
hydra token user \
--client-id auth-code-client \
--client-secret secret \
--endpoint http://127.0.0.1:4444/ \
--port 5555 \
--scope openid,offline
version: '3.7'

services:
hydra-migrate:
image: oryd/hydra:v1.9.0
restart: on-failure
networks:
- ory-hydra-network
command:
migrate sql -e --yes
environment:
- DSN=postgres://hydra:secret@postgresd:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4
depends_on:
- postgresd

hydra:
image: oryd/hydra:v1.9.0
restart: on-failure
networks:
- ory-hydra-network
ports:
- "4444:4444" # Public port
- "4445:4445" # Admin port
- "5555:5555" # Port for hydra token user, testing purpose only
command:
serve all --dangerous-force-http
environment:
# https://www.ory.sh/hydra/docs/reference/configuration
# https://github.com/ory/hydra/blob/aeecfe1c8f/test/e2e/docker-compose.yml
- SECRETS_SYSTEM=this-is-the-primary-secret
- URLS_LOGIN=http://localhost:8000/authentication/login # Sets the login endpoint of the User Login & Consent flow.
- URLS_CONSENT=http://localhost:8000/authentication/consent # Sets the consent endpoint of the User Login & Consent flow.

# set to Hydra public domain
- URLS_SELF_PUBLIC=http://localhost:4444 # to public endpoint
- URLS_SELF_ISSUER=http://localhost:4444 # to public endpoint
- DSN=postgres://hydra:secret@postgresd:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4
- SERVE_PUBLIC_PORT=4444
- SERVE_PUBLIC_HOST=0.0.0.0
- SERVE_PUBLIC_CORS_ENABLED=true
- SERVE_ADMIN_PORT=4445
- LOG_LEVEL=debug
- TRACING_PROVIDER=jaeger
- TRACING_PROVIDERS_JAEGER_SAMPLING_SERVER_URL=http://jaeger:5778/sampling
- TRACING_PROVIDERS_JAEGER_LOCAL_AGENT_ADDRESS=jaeger:6831
- TRACING_PROVIDERS_JAEGER_SAMPLING_TYPE=const
- TRACING_PROVIDERS_JAEGER_SAMPLING_VALUE=1
depends_on:
- postgresd
- jaeger

postgresd:
image: postgres:13
restart: on-failure
networks:
- ory-hydra-network
ports:
- "5433:5432"
environment:
- POSTGRES_USER=hydra
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=hydra
volumes:
- ./logs:/var/lib/postgresql/data

adminer:
image: adminer
restart: always
networks:
- ory-hydra-network
ports:
- 9000:8080

jaeger:
image: jaegertracing/all-in-one:1.7.0
restart: on-failure
networks:
- ory-hydra-network
ports:
- 5775:5775/udp
- 6831:6831/udp
- 6832:6832/udp
- 16686:16686 # Web App GUI to see traces

networks:
ory-hydra-network:
name: ory-hydra-net
$ git clone git@github.com:yusufsyaifudin/oauth2-example-hydra.git
$ cd oauth-example-hydra
$ git checkout v0.1.0
$ go mod download
$ go run cmd/authc/main.go
curl -X POST 'http://localhost:4445/clients' \
-H 'Content-Type: application/json' \
--data-raw '{
"client_id": "myclient",
"client_name": "MyApp",
"client_secret": "mysecret",
"grant_types": ["authorization_code", "refresh_token"],
"redirect_uris": ["http://localhost:1234/callbacks"],
"response_types": ["code", "id_token"],
"scope": "offline users.write users.read users.edit users.delete",
"token_endpoint_auth_method": "client_secret_post"
}'
REDIRECT_URL=http://localhost:1234/callbacks CLIENT_ID=myclient CLIENT_SECRET=mysecret go run cmd/frontend/main.go
Figure 5. Left tab is for docker compose, top-right tab is for Identity Provider run in port 8000, bottom-right is for Front-End run in port 1234.
Figure 6. Page show “Click here to _Sign In with YourApplicationName_”
Figure 7. A Login Page in Identity Provider.
Figure 8. A Consent Page in Identity Provider
Figure 9. A Front-End callback retrieve a authorization code and state to be validated.

References which also good to read

--

--

--

Yogyakarta — Indonesia

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Yusuf Syaifudin

Yusuf Syaifudin

Yogyakarta — Indonesia

More from Medium

gorilla/mux 101 (rk-boot): Swagger UI

Go with gRPC, SQL, Dependency Injection(Wire) + standard project structure, and more: Introduction.

Functions in GO

Unit Testing & Debugging Setup For Golang with VS Code