OAuth2 implementation with ORY Hydra, Vapor 3 and iOS 12

Part 3: Setup Identity Provider

Hannah Teuteberg
12plus1
9 min readJul 30, 2021

--

Note: This post was originally written in 2018 and has not been updated since then. So this is not state of the art anymore, but maybe it can still be helpful to some people.

After we successfully implemented the user management in Part 2, we’ll now need to add the implementation of the Identity Provider for ORY Hydra on our Vapor backend.

Prerequisites

You should checkout the previous parts of the series:

Create Identity provider

As explained in Part 1 of this tutorial, ORY Hydra expects its identity provider to react to GET calls on /auth/registeror /auth/login and /auth/consent. When one of the first two endpoints is called, we want to present the user with a web interface where he can register/login if necessary. For the consent endpoint we don’t want to present any UI, but just accept the consent request as we are working with a first party app. ORY Hydra will decide whether or not the user needs to login again (see Authorization Flow diagram in Part 1).

To make sure our Vapor backend reacts to those endpoints, we need to add them to the boot function in our AuthController and add the related functions.

You’ll see three errors because the functions declare to return Future<Response> even though they are not doing that yet. We’ll get to that a little later.

Getting and accepting the LoginRequest

When the login and register endpoints are called, the first thing we need to do is to ask ORY Hydra whether or not we are supposed to present the user with a UI to login or if that is not needed. We can do that via accessing Hydra’s administrative API endpoint for receiving a login request /oauth2/auth/requests/login.

Create Model and Service

As we now start interacting with Hydra quite a lot and we don’t want to mess up our AuthController with all that code, the first thing we gonna do is to create a new class called HydraService.

HydraService.swift

To be able to access that service we’ll need to register it in configure.swift.

Add this in line 8 in configure.swift

First thing we want to be able to do is receive a login request from Hydra. Let’s create a model for that request.

HydraLoginRequest.swift

Hydra is able to return even more stuff to us when asking for a login request, but these are the three parameters we care about.

  • challenge: This is the identifier of the authentication request. It is used to identify the session.
  • skip: If true, this Bool indicates that there is no need for the user to login again. So we can use this to know whether or not to show the login/register UI.
  • subject: This String usually contains the user ID of the user who asked for the login request. We’ll need that information in a later step of the auth flow.

Get LoginRequest from Hydra

Now we should create a function in the HydraService to get the login request from Hydra:

Get login request in HydraService.swift

That is quite a lot of code, let’s go through it step by step.

  • first we create the baseUrl which works with an environment variable you need to add in the run scheme in XCode. Via this URL the administrative API of ORY Hydra can be reached.
Create environment variable for hydra base URL in Xcode
  • then we create a small function to deliver the URL for the login request endpoint with a certain challenge
  • then we create a function to perform to get the login request
  • in the end we create another function to extract the challenge from the request
  • We also add a convenience function on Future in a new helper file to map the response:
convenience helper as extension on Future

Render UI for register + login

Now we can go back to the AuthController and use the HydraService in the renderRegister and renderLoginfunctions. In both cases the interaction with Hydra is the same, just the UI we show to the user and our user management is different.

renderAuth function in AuthController.swift

First we are creating a HydraService instance and extract the challenge from the request. The we try to get the LoginRequest via the HydraService. We ignore the skip flag for now and just always try to render the authentication UI by adding the following:

render authentication UI in AuthController.swift

The above code just looks for an HTML file called either register or login. We used Leaf for creating the HTML files. You can check them out in the Github repository, but we won’t go into detail on how to create them and what needs to be done to add Leaf to the Vapor project. It is just two basic forms that trigger POST calls on our user management endpoints for registering and logging in a user when they are submitted. The only specific detail you need to know is that they need to include the challenge as a hidden input field, so we can extract it from there again later.

Now we don’t want to ignore the skip value anymore, but only want to show the UI if skip is false. If it is true, we’ll need to tell Hydra to accept the login request and trigger the consent step afterwards.

Accepting the LoginRequest

There is quite a few things we need to do to accept a login request. Let’s start in the AuthController again and change the renderAuth function to handle the skip flag:

renderAuth function in AuthController.swift including accepting the login request

What we changed in here is the following:

  • we check if the login UI should be shown via the skip flag in the login request
  • if skip is false, we show the UI as before
  • if skip is true, we want to accept the login request and trigger the consent step afterwards

Now we need to switch back to the HydraService and implement acceptLoginRequest.

accept login request in HydraService.swift

In the HydraService we added another convenience function for receiving the URL for the accept login request endpoint of Hydra. Then we created the actual function to accept the request. We map the response into a HydraRedirect object, which only includes the redirect URL.

Create Payload model for accepting LoginRequest

In the request we need to include some parameters that Hydra expects to get. To be able to include them this way, we need to add another struct in our HydraLoginRequest model:

added HydraAcceptLoginRequestPayload in HydraLoginRequest.swift

Our new struct HydraAcceptLoginRequestPayload includes three properties:

  • remember: this Bool, if true, tells Hydra to remember the user by storing a cookie with authentication data in the browser
  • remember_for: this tells Hydra how long it should remember the user. When set to 0, it will be remembered indefinitely
  • subject: the same subject as in the login request

In addition to this, we need to add two more convenience functions in extensions on Client and Request. Create two new files for these in the Helpers folder and add the following code:

extension on Request in RequestWith.swift
extension on Client in Client.swift

In case we want to skip the register/login UI and directly accept the login request, we are done. We still need to accept the login request after the user registered/logged in though.

Accept LoginRequest after registration/login

To do so, we need to adjust the authenticate function in the AuthController to accept the login request after a successful register/login. Create a new function in the AuthController called loginOrRegister that wraps the code for the user management:

wrapper function for user management part in authenticate function in AuthController.swift

Now change the authenticate function to accept the login request:

authenticate function including accepting login request in AuthController.swift
  • We changed the return type to Future<Response>. We need to do that to be able to call renderAuth again in case accepting the login request fails.
  • We first do the user management part of registering/logging in as before.
  • We then tell the HydraService to accept the login request. You might notice that the signature of this function is different than the one we used before. We’ll get to that soon.
  • In case of an error, we want to return the user to the register UI and show an error message. To be able to do so, we need to pass the challenge to the renderAuth function like this:
renderAuth function accepting challenge parameter in AuthController.swift

As mentioned, the acceptLoginRequest function in the HydraService is not the same we used before. This is because we don’t have access to the HydraLoginRequest in this case. Therefore we need to add another function for accepting the login request with a different signature in the HydraService.

additional acceptLoginRequest function in HydraService.swift

As you can see, we now try to access the login challenge via the request’s payload. To be able to do so, we need to adjust our LoginPayload to contain the challenge.

updated LoginPayload in AuthController.swift

We just added a new property called challenge of type String and added a validation to make sure this property can not be empty.

That’s it for the case where the user has to actively login. The next step will be to implement something similar for the consent part.

Getting and accepting the ConsentRequest

After we successfully accepted the login request, Hydra will start the consent step by making a GET request to /auth/consent on our API. So we’ll need to handle that request. The difference compared to the login flow is that this time we never want to show a UI to the user, but always directly accept the consent request as our auth server is only interacting with our own app for now.

Create ConsentRequest model

The first step is o create a model for the HydraConsentRequest similar to the login request we already have. Create a new file and add the following:

HydraConsentRequest.swift

The only difference compared to the HydraLoginRequest is that we don’t have the subject anymore, but instead the requested_scope and grant_scope. Both of these describe the scope that the user grants access to.

Getting + accepting the ConsentRequest in Service

Now go back to the HydraService and add functions for getting and accepting the consent request. To do so, we need to adjust our convenience functions that return the necessary URLs to work with login and consent like this:

modified convenience functions in HydraService.swift to fit login and consent

We introduced a new type called HydraRequestType and use that in the convenience functions to return proper URLs for both, login and consent. Next we are going to add functions for getting and accepting the consent request, which work similarly to the login functions.

get and accept consent request in HydraService.swift

Go back to the AuthController and adjust the skipConsent function.

skip consent function in AuthController.swift

What we do in here is the following:

  • we get the consent request from Hydra
  • independant of what we receive in the response, we always directly accept the request afterwards
  • after that we respond with a redirect

After the consent request is accepted, Hydra will redirect the user back into the iOS app with the requested tokens.

Protection against CSRF

In general, we would now be done with the authorization flow implementation on our Vapor backend. There is one more thing we should add for security reasons though. We should protect the user from Cross-site request forgery (CSRF).

Add CSRF library

To do so we need to add CSRF tokens to our HTML forms as hidden input fields. There is a convenience library for Vapor that takes care of creating those tokens called CSRF. Let’s add that to our project as a dependency.

To be able to use it, we’ll need to adjust our configure.swift file to register CSRF as a service and add it and SessionMiddleware as middleware.

add CSRF and SessionMiddleware in configure.swift

Before implementing adding a CSRF token to our login/register forms, let’s roughly go through what the CSRF library does. It can be used to create and validate CSRF tokens. As we registered it as a middleware for our requests, we don’t need to actively trigger the validation, the library will take care of that automatically. What we do need to do though is to create a CSRF token and add it to our HTML forms as a hidden input field.

Add CSRF token to login + register forms

So, we need to add the CSRF token to our login/register forms in the AuthController. Open it and create an instance of CSRF at the top of the file:

create CSRF instance in AuthController.swift

Then navigate to the renderAuthForm function and change it to the following:

renderAuthForm function in AuthController.swift

What do we do here exactly?

  • We first get the session out of the request and save it.
  • Then we check if the request’s cookies contain a CSRF token, that was generated by Hydra. We need that token to be able to verify the request came from our own service.
  • We save Hydra’s CSRF token under a key specific to the CSRF library in the session.
  • Then we create a new token from this request using the CSRF library and pass it as a view variable to our HTML form.

CSRF will now get Hydra’s CSRF token out of the session, use it to create a token and make sure that the token passed to the HTML form is matching the one created using Hydra’s one. This way we can be sure, the request came from our own service.

This is it! We implemented all parts necessary to run the authorization flow with our vapor backend. In the next part we’ll have a look at the implementation of the iOS client.

Further reading

Hydra Node.js example for Identity provider:
https://github.com/ory/hydra-login-consent-node

What is CSRF:
https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)

Resources

You can find the fully implemented Vapor backend for this tutorial on Github!

Stay updated

If you liked this tutorial and are interested in further articles from us, follow us here or on Twitter and checkout our website!

--

--

Hannah Teuteberg
12plus1
0 Followers
Editor for

Co-Founder of 12plus1, iOS development, Swift