Documentation

Server 3.x

Client Implementation Notes#

In order to understand how to best create an implementation of an eduVPN and Let’s Connect! client, we collected some notes that will hopefully help you create a better client.

This builds on the following documents:

API#

This section expands upon the API document by giving some client-specific implementation notes.

Standards#

As said, the server uses a simple HTTP API protected by OAuth 2, following all recommendations of the OAuth 2.1 specification.

Please follow the OAuth specification closely, or use a library for your platform that implements OAuth 2.1.

See section 10 of that document for a high level overview of the changes from OAuth 2, it basically boils down:

For the server API you MUST use HTTPS.

Flow#

The server API was described in API. Below we describe how the application MUST interact with the API. It does NOT include information on how to handle OAuth. The application MUST properly handle OAuth, including error cases both during the authorization, when using a “Refresh Token” and when using the API with an “Access Token”.

This is the “initial flow” when connecting to a new server:

  1. Call /info to retrieve a list of available VPN profiles for the user;
  2. Show the available profiles to the user if there is >1 profile, the user MUST choose a profile, there MUST NOT be a default already selected. Show “No Profiles Available for your Account” when there are no profiles;
  3. After the user chose (or there was only 1 profile) perform the /connect call as per Connect;
  4. Store the configuration file from the response. Make note of the value of the Expires response header to be able to figure out how long you are able to use this VPN configuration;
  5. Connect to the VPN;
  6. Wait for the user to request to disconnect the VPN…;
  7. Disconnect the VPN;
  8. Call /disconnect;
  9. Delete the stored configuration file and its expiry time.

As long as the configuration is not “expired”, according to the Expires response header the same configuration SHOULD be used until the user manually decides to disconnect. This means that during suspend, or temporary unavailable network, the same configuration SHOULD be used. The application SHOULD implement “online detection” to be able to figure out whether the VPN allows any traffic over it or not.

The basic rules:

  1. /connect (and /disconnect) ONLY need to be called when the user decides to connect/disconnect/renew, not when this happens automatically for whatever reason, e.g. suspending the device, network not available, …;
  2. There are no API calls as long as the VPN is (supposed to be) up (or down).

The application SHOULD implement “auto connect” (device or application) start-up and SHOULD remember the last user chosen profile. It MUST call /info and /connect as well! The /info call to be sure the profile is still available (for the user) and the /connect to obtain a configuration. This does NOT apply when the application configures a “system VPN” that also runs without the VPN application being active. The application MUST implement a means to notify the user when the (system VPN) configuration is about to expire.

It can of course happen that the VPN is not working when using the VPN configuration that is not yet expired. In that case the client SHOULD inform the user about this, e.g. through a notification that possibly opens the application if not yet open. This allows the user to (manually) disconnect/connect again restoring the VPN and possibly renewing the authorization when e.g. the authorization was revoked.

Handling error responses#

The server API can have many different error responses. Do NOT use the exact “Message” for string comparison in your application code. Simply checking for e.g. 4xx errors should suffice.

Errors which the client cannot handle, e.g. 5xx errors should probably be shown as a “server error” to the user. Possibly with a “Try Again” button. The exact error response MUST be logged and accessible by the user if so instructed by the support desk, and MAY be shown to the user in full, however a generic “Server Error” could be considered as well, perhaps with a “Details…” button.

Connecting#

When getting a configuration to connect. you MUST use the Expires response header value to figure out how long the VPN session will be valid for. When implementing the client, make sure you never connect to the VPN server with an expired VPN configuration.

To select which type of protocol to connect to, use the accept header, e.g. Accept: application/x-openvpn-profile to indicate your client only supports OpenVPN.

Before using a WireGuard configuration, your locally generated private key needs to be added under the [Interface] section, e.g.:

[Interface]
PrivateKey = AJmdZTXhNRwMT1CEvXys2T9SNYnXUG2niJVT4biXaX0=

...

Generating WireGuard Keys#

To generate WireGuard keypairs you MAY use libsodium’s crypto_box_keypair() and extract the public key using crypto_box_publickey() instead of using exec() to run the wg tool.

You MUST NOT use the same WireGuard keypair between different servers. You MUST NOT use the same WireGuard keypair between different OAuth authorizations. You MAY reuse the keypair for the same OAuth authorization. You SHOULD create a new keypair for each call to /connect.

For APIv4 you MUST use the public key of a freshly generated keypair for every call to /connect.

Disconnecting#

The Server /disconnect API MUST ONLY be called when the user decides to stop the VPN connection:

  1. The user toggles the VPN connection to “off” in the application;
  2. The user switches to another profile, or server;
  3. The user quits the VPN application
  4. The users reboots the device while the VPN is active (implicit application quiting)

After calling this method you MUST NOT use the same configuration again to attempt to connect to the VPN server. First call /info and /connect again.

This call is “best effort”, i.e. it is not a huge deal when the call fails. No special care has to be taken when this call fails, e.g. the connection is dead, or the application crashes.

This call MUST be executed after the VPN connection itself has been terminated by the application, if that is possible.

When talking about “System VPNs”, i.e. VPN connections that are not controlled by the user, but by the device administrator, or possibly explicitly configured as a “System VPN” by the user, if available, these rules do not apply.

Expiry#

The reason for discussing session expiry is that we want to avoid a user’s VPN connection terminating unexpectedly, e.g. in the middle of a video conference call.

In order to help the user avoiding unexpected VPN connection drops, the client implements:

  1. A countdown timer that shows how long the VPN session will still be valid for so the user is made aware of upcoming expiry;
  2. A “Renew Session” button that allows the user to “refresh” the VPN session at a convenient time;
  3. An OS notification that informs the user when the expiry is imminent, or has already occurred.
What Visible
Countdown Timer ${SESSION_EXPIRES_AT} - ${NOW} <= 24:00:00
“Renew Session” Button ${SESSION_EXPIRES_AT} - ${NOW} <= 24:00:00 AND ${NOW} - ${SESSION_STARTED_AT} >= 00:30:00
OS Notification ${SESSION_EXPIRES_AT} - ${NOW} IN {04:00:00, 02:00:00, 01:00:00, 00:00:00}

With ${NOW} we mean the current time stamp. With ${SESSION_STARTED_AT} we mean the moment the OAuth authorization completed, i.e. the client obtained their first OAuth access token. With ${SESSION_EXPIRES_AT} we mean the time the session expires, as obtained from the Expires HTTP response header part of the /connect call response.

In addition to the “Countdown Timer” visible in the main application window, there is also a timer under “Connection Info” in the UI. This timer is always visible.

When the user clicks the “Renew Session” button the following MUST happen in this order:

  1. Disconnect the active VPN connection;
  2. Call /disconnect;
  3. Delete the OAuth access and refresh token;
  4. Start the OAuth authorization flow;
  5. Automatically reconnect to the server and profile if (and only if) the client was previously connected.

The OS notification shown to the user MAY offer the “Renew Session” button inside the notification as well, if supported by the OS.

Connection Trouble#

When the VPN connection is established over UDP, it may appear to work, but doesn’t in reality. This can be, at least, for two reasons:

  1. UDP is completely blocked on the network the VPN client is on;
  2. There are MTU problems on the network, where simple protocols like ping work, but e.g. browsing the web does not.

Previously our VPN clients had a global setting called “Prefer TCP” that would make the VPN client always connect over TCP. We assume that once the “Prefer TCP” toggle is switched on and the VPN works, the user will never disable it anymore, even if they are mostly working from networks that have no problems with UDP.

We prefer VPN clients to connect over UDP as much as possible, as that will reduce the used resources on the server, and improve performance.

In the ideal scenario the users will never notice anything and the client will automatically switch to TCP if it detects that UDP is not working properly. However, this is quite complicated to properly implement. It most likely will also introduce a delay when establishing a connection.

Therefore, the VPN clients should do the following in order of most importance, the key point being that the user always remains in control:

  1. The VPN client always starts by connecting over UDP
  2. Allow the user to (immediately) override with a “Connect over TCP” button if it turns out the VPN is not working for them;
  3. Implement a “Connection Check” that will automatically switch to TCP in case UDP does not work properly;
  4. The client remembers that on a particular network UDP does not work and immediately uses TCP from the start;
  5. The VPN client monitors the active connection to make sure it keeps working.

Step (2) allows the user to be in control. If they connect to the VPN from an airport they may already know it won’t work over UDP, this allows them to cut the connection check short and get a working VPN immediately.

With step (3) the user doesn’t need to do anything and they’ll end up with a working VPN after the online detection completes. Performing the online detection may take a while, possibly up to 10 seconds.

Implementing step (4) can be quite challenging and we should be explicit about what “a particular network” means. Is it the BSSID, the SSID, or the MAC address of the first hop. We also should make sure it is actually possible to extract this information from the host OS.

Step (5) is important for example with the scenario where the VPN client is roaming. They connected to the VPN server over UDP on a network that has no issues, but move to another network that does have UDP issues.