OAuth2 with ORY Hydra, Vapor 3 and iOS 12
Part 1: Introduction and setup of ORY Hydra authorization server
In this tutorial we try to give you a broad understanding on how to implement the OAuth2 authorization code flow with an iOS app, a Vapor API and Hydra as the OAuth2 server.
Part 1: Introduction and setup of ORY Hydra authorization server (you are here)
Part 2: User management in Vapor backend
Part 3: Set up Vapor backend as identity provider for ORY Hydra
Part 4: Set up OAuth2 authorization on iOS with AppAuth
- You need a Mac, at least for building the iOS app. It is also helpful for debugging the Vapor backend.
- You need to have Docker installed (https://www.docker.com/get-started)
- You should have a basic understanding of how the OAuth2 authorization code flow with OpenID Connect works. There is plenty of resources on this, you could checkout this introduction by DigitalOcean for example.
- Note that this tutorial will just set up everything on your local machine and is considered non-production-ready. For example, we will store the client secret in the iOS app, which you should never do. If there will be demand for it, we might do an additional part of this tutorial that focuses on making the whole setup production ready.
We are working on a product where security is a concern, so relying on best practice user authentication is definitely the way to go. Setting up OAuth2 usually is a big hassle though. ORY Hydra relieves you of the burden of implementing your own OAuth2 server, but still requires/allows you to use your own identity provider. This caused some confusion for us about which parts of the auth flow need to be implemented in our backend as part of the identity provider and which parts are provided by Hydra. In this tutorial we’ll try to make it a little bit clearer what responsibilities each part handles when developing an iOS app with a Vapor backend and Hydra as your OAuth2 server.
This is what the app will look like when we are finished:
Closer look at the 3 components
The iOS app will be very simple in nature and will just feature two native screens: The login prompt screen and the success screen that shows you that you just authenticated your user successfully. We will use AppAuth for handling OAuth tokens inside the app.
The Vapor backend will contain the user management and also serve as an identity and consent provider to deliver the HTML Login and Register screens. The consent step will be automatically skipped as this is not needed for a first party app like this one. We can assume that a user that wants to login on our platform also wants to give it access rights to his data.
Hydra is an open source OAuth2 server that manages the authorization flow by delegating user authentication (login, register) to the identity provider (Vapor backend in our case). In case of success it will issue access, refresh and id tokens, that can be used to authenticate requests to access restricted data (i.e. user profile) on the backend.
This might already be confusing, so let’s try to understand it better by breaking down what is needed for the setup:
- The iOS app needs to know the Hydra server’s auth and token endpoints (Public API), which are completely managed by the AppAuth library though.
- The Vapor backend needs to have access to the Hydra server’s admin endpoints (Administrative API) to initiate and accept login and consent requests.
- Hydra needs to know the login (or register) endpoint and the consent endpoint on your Vapor backend, so it can delegate authentication to your identity provider. The client you create in Hydra also needs a callback url so that Hydra knows where to redirect when the authentication process is done (this will be a custom url scheme, that links into the iOS app).
Let’s look at the interaction flow that should happen when a user wants to register an account:
Note that the star-prefixed “entity names” are not actual types but are just used to reference the same piece of code/data at different stages (e.g. “*AuthCallback”, “*RegistrationInput”, …).
These diagrams mostly serve as an overview you can come back to over the whole course of the tutorial series. It is a lot to take in, but keep in mind that we just have to implement the non-dotted fields and set up the blue Hydra part. The latter is what we are focussing on for the rest of this part of the tutorial.
Let’s get started
Hopefully, now you have a better understanding of what we are going to build here. Now it’s time to dive into the actual implementation. Well…not really. As Hydra is already implemented, it just needs to be started with some parameters and you need to create a client for your application.
Starting a Hydra instance
We will start a Hydra server locally and roughly follow the steps from the official example node.js app.
First we will pull the latest Hydra docker image (v1.0.0-beta.9 as of this writing):
$ docker pull oryd/hydra:v1.0.0-beta.9
Next we want to start a local Hydra instance on our machine:
$ docker run -it --rm --name hydra-example -p 4444:4444 -p 4445:4445 \
-e OAUTH2_SHARE_ERROR_DEBUG=1 \
-e LOG_LEVEL=debug \
-e OAUTH2_CONSENT_URL=http://localhost:8080/auth/consent \
-e OAUTH2_LOGIN_URL=http://localhost:8080/auth/login \
-e OAUTH2_ISSUER_URL=http://localhost:4444 \
-e DATABASE_URL=memory \
oryd/hydra:v1.0.0-beta.9 serve all --dangerous-force-http
Let’s have a look at the different parameters in that command:
--name: we give the instance a name, in this case hydra-example
-p 4444:4444: exposes Hydra’s public API on
-p 4445:4445: exposes Hydra’s administrative API on
OAUTH2_CONSENT_URL: defines the URL of the consent provider. The service that handles this URL is our own API, which we’ll implement in Part 2 of this tutorial series
OAUTH2_LOGIN_URL: defines the URL of the login provider. The service that handles this URL is our own API, which we’ll implement in Part 2 of this tutorial series
OAUTH2_ISSUER_URL: is the URL of the public API of Hydra (it can be used to discover the public API endpoints, but we’re not going to use this ability in the tutorial)
DATABASE_URL: for our local instance we just save all data in memory
--dangerous-force-http: this flag disables SSL for testing purposes
The output of that command should look like this:
Nice, that’s it to run our authorization server!
Creating an OAuth2 client
Next we’ll want to register a client that we can use for testing. Client in this case does not refer to the iOS client, but to a client of the authorization server. Imagine for example GitHub, which is providing their authorization endpoints to third-party applications. If you would want to integrate a GitHub login in your service, you would need to register your service as a client of GitHub’s authorization server.
Even though we’ll use our authorization server only for our own service, we still need to register that service as a client, so the authorization server knows it is allowed to ask for tokens.
To create a test client, run the following command:
$ docker run --link hydra-example:hydra oryd/hydra:v1.0.0-beta.9 clients create \
--endpoint http://hydra:4445 \
--id test-client \
--secret test-secret \
--response-types code,id_token \
--grant-types refresh_token,authorization_code \
--scope openid,offline \
--link: this tells which Hydra instance the client should be created on, in our case hydra-example, the one that we created before
—-endpoint: this is the URL of Hydra’s administrative API
—-id: this is the name of the client you want to register, in our case test-client
—-secret: this is the secret of the client you want to register, in our case test-secret
—-response-types: this defines what kind of responses we want to receive. In our case we want to receive authorize codes (
code) and OpenID Connect ID Tokens (
—-grant-types: this defnes which OAuth2 flows we want to perform. In our case the authorization code flow (
authorization_code) that also includes refresh tokens (
--scope: this defines which permissions the client should have. In our case we need to allow the client to perform the OpenID Connect flow (
openid) and to refresh expired tokens (
—-callbacks: this defines the redirect URI that is called by Hydra when the authorization flow was completed successfully. We’ll need to register this URL scheme in our iOS app later.
The output of that command should look like this:
And in the logs of our authorization server you should also see that the request was received:
That’s it! Our authorization server is up and running with a usable client!
In itself it won’t do much, because, remember, it relies heavily on redirection to the identity/consent provider. In the next part we’ll take a look at how to implement exactly that, using Vapor.
User authentication with OAuth2 and OpenID Connect
Securely set up OAuth2 for Mobile Apps
OpenID Connect client implementation guide
ORY Developer Guide:
Hydra Node.js example:
ORY Hydra API docs: