Golang: OAuth2 & OpenID
In my upcoming articles, I implemented authentication on the API Gateway side using JWT tokens.
But it’s time to take a big step forward (of course with little effort)
It’s time to implement OAuth2 and OpenID protocol to access our resources. And we will no longer have to solve the problem with revoked tokens and many more with what.
Of course, we don’t have much time at home to implement these protocols, and we also didn’t want to create new errors, so we’ll take the finished certified product ORY Hydra.
What we need:
- Docker
- ORY Hydra
- Identity&Consent provider (our service)
The resources to which we request access will be our many services and their APIs.
We will validate and update the received tokens on the API Gateway and we will not consider this article.
OAuth2
What is the OAuth2 protocol, we will not particularly consider. I will only remind you that it was invented by some smart people and implemented by others, which are still far from us :)
OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices.
ORY Hydra
This is a wonderful open source application that will do all the magic for us on implementing the OAuth2 & OpenID protocols.
Hydra is an OAuth 2.0 and OpenID Connect Provider. In other words, an implementation of the OAuth 2.0 Authorization Framework as well as the OpenID Connect Core 1.0 framework. As such, it issues OAuth 2.0 Access, Refresh, and ID Tokens that enable third-parties to access your APIs in the name of your users.
Why do we need OpenID?
OpenID a simple credential layer on top of OAuth, сonnect allows clients of all types, to receive information about authenticated sessions and end users.
In our example, we will store additional user information that we can always get from Hydra.
OAuth Grant Types
OAuth2.0, as written above, this authorization protocol supports several interaction scenarios.
- Authorization Code
- Client Credentials
- Device Code
- Refresh Token
We are interested in the first, most common option with an authorization code. It is just right for our interaction scenario. Good for server-side applications.
Let’s get started
The first thing we need to do is deploy Hydra. The most convenient option, of course, is Docker.
We’ll clone her project and take a look at the numerous options for docker-compose files.
git clone git@github.com:ory/hydra.git
You can run Hydra with different types of databases. But if you already have a database of data, then you can select a separate scheme in it and register DSN. Or maybe you will like to store everything you need in memory:
version: '3'
services:
hydra:
image: oryd/hydra:1.4.8
ports:
- "4444:4444" # Public port
- "4445:4445" # Admin port
command:
serve all --dangerous-force-http
environment:
- URLS_SELF_ISSUER=http://127.0.0.1:4444
- URLS_CONSENT=http://127.0.0.1:3000/consent
- URLS_LOGIN=http://127.0.0.1:3000/login
- URLS_LOGOUT=http://127.0.0.1:3000/logout
- DSN=memory
- SECRETS_SYSTEM=youReallyNeedToChangeThis
- OIDC_SUBJECT_IDENTIFIERS_SUPPORTED_TYPES=public,pairwise
- OIDC_SUBJECT_IDENTIFIERS_PAIRWISE_SALT=youReallyNeedToChangeThis
restart: unless-stopped
What else do we see in this configuration?
serve all --dangerous-force-http
We allow communication through HTTP. Understand this is not safe!
Read the documentation for running the serve command.
ORY Hydra exposes two ports, a public and an administrative port. The public port is responsible for handling requests from the public internet, such as the OAuth 2.0 Authorize and Token URLs. The administrative port handles administrative requests like creating OAuth 2.0 Clients, managing JSON Web Keys, and managing User Login and Consent sessions.
Start the service with the parameter all (both ports) for the house project just right.
Let’s look at environment variables:
URLS_SELF_ISSUER — this is the hydra public API
URLS_CONSENT — content provider (method consent)
URLS_LOGIN — identity provider(method login)
URLS_LOGOUT — identity provider(method logout)
OAUTH2_ERROR_URL — authorization error page
It is worth noting that the implementation of business logic is identification, rendering interfaces (login,registration ..pages)— falls on your shoulders. Hydra, you only tell what address that is located. And already she takes care of the rest. And please do not implement any sessions on the side of your Identity Providers — this also takes Hydra.
For a quick start, run Hydra together with PostgreSQL. Just let’s remove form quickstart.yml the service consent from the file — we will write it ourselves on Golang. The file should turn out as we considered above.
We execute from the root of the Hydra project:
docker-compose -f quickstart.yml \
-f quickstart-postgres.yml \
up --build
Next we need to create a grant issuer:
docker-compose -f quickstart.yml exec hydra \
hydra clients create \
--endpoint http://127.0.0.1:4445 \
--id auth-code-client \
--secret secret \
--grant-types authorization_code,refresh_token \
--response-types code,id_token \
--scope openid,offline \
--callbacks http://127.0.0.1:5555/callback
Note that you need to add
--token-endpoint-auth-method none
if your clients are public (such as SPA apps and native apps) because the public clients cannot provide client secrets.
What else do we see here?
grand-types — grants for acquiring an access token.
scope (openid, offline)— openid: request an OpenID Connect ID Token, offline: required when requesting refresh tokens
response-types — this is what we want to get in the end.
callbacks — the address to which the Get-parameter will ultimately return the authorization code to us
Sign in
For example, click the button to enter your application and get to the Hydra at this address:
http://127.0.0.1:4444/oauth2/auth?audience=&client_id=scum-client&redirect_uri=http://127.0.0.1:5555/callback&response_type=code&scope=openid+offline&state=pnaqqipwwpbrdkosbqflsnya
Here we are all familiar with the parameters.
As soon as we get to the Hydra, it checks whether we have cookies set for successful login earlier and in any case redirect to the URLS_LOGIN set in its configuration.
Our identity-provider intercepts this request, takes the login_challenge from it and goes with it to the hydra, thereby confirming the running login process.
By the way, the Hydra project already has a kindly generated HTTP-client by swagger.
We get the challenge from the query and make a request with it.
In the description to the code I will indicate a link to the Hydra API.
A request comes from Hydra:
http://127.0.0.1:3000/login?login_challenge=123
Here we draw the login page if the user has not logged in yet and in case of successful entry of data, we complete the login process - confirm the started process and indicate the duration of the login.
If everything went without errors, we redirect to the address that came to us in the response object.
Next, the flow returns us to the consent handler and the login-like process is repeated.
Hydra throws the user to:
http://127.0.0.1:3000/consent?consent_challenge=123
Next, you can display an interface with the confirmation of the requested scope, or if this is your application, then allow everything that was requested.
We confirm the request. And note that you can also save additional information about the user here.
Later, by completing this request, you will be able to get all the additional information saved about users:
https://www.ory.sh/hydra/docs/reference/api/#openid-connect-userinfo
Well, you can be congratulated! Since if everything is ok on callback url the authorization code set earlier will return to you!
And with the authorization code, you can already get the access token and refresh token:
https://www.ory.sh/hydra/docs/reference/api#the-oauth-20-token-endpoint
The logout process will be your homework :)
You can view the repository with an example here.