APIv4 (Draft)#
NOTE: Work in Progress!
This document describes version 4 of the API provided by eduVPN and Let’s Connect! servers.
See Changes from APIv3 for a list of changes since our current APIv3.
The API is intended to be used by the eduVPN and Let’s Connect! applications. If you are creating your own application, look here how to register your own client in the server.
Using this document you should be able to implement the API in your VPN client, or provide the same API for your VPN server to leverage the existing VPN clients.
Standards#
We use a simple HTTP API protected by OAuth 2, following all recommendations of the OAuth 2.1 draft specification.
For some further implementation notes and recommendations for the client, please read this document.
Server Discovery#
As there are many servers running eduVPN / Let’s Connect! you need to know which server you need to connect to. This can be either hard-coded in the application, the user can be asked to provide a server address or a “discovery” can be implemented.
For eduVPN specific we implement “server discovery” as documented here.
Server Endpoint Discovery#
A “well-known” URL is provided to figure out the OAuth and API endpoint one
has to use. The document can be retrieved from /.well-known/vpn-user-portal
,
e.g.:
{
"api": {
"http://eduvpn.org/api#4": {
"api_endpoint": "https://vpn.example.org/vpn-user-portal/api/v4",
"authorization_endpoint": "https://vpn.example.org/vpn-user-portal/oauth/authorize",
"token_endpoint": "https://vpn.example.org/vpn-user-portal/oauth/token"
}
},
"v": "4.0.0-1.fc41"
}
Servers that provide the http://eduvpn.org/api#4
key under api
, support
this API.
The application MUST retrieve this document at least once per application run, i.e. if the user restarts the application this document MUST be retrieved fresh for each server the client interacts with. An application MAY opt to refresh the document more frequently.
Endpoint Location#
When fetching this document, redirects, e.g. 301
, 302
, 303
, MUST be
followed, but MUST NOT allow redirect to anything else than other https://
URLs, e.g. redirects to http://
MUST be rejected.
Authorization Endpoint#
The authorization_endpoint
is used to obtain an authorization code through an
“Authorization Request”. The following parameters MUST be set:
client_id
;redirect_uri
;response_type
: MUST becode
;scope
: MUST beconfig
;state
;code_challenge_method
: MUST beS256
;code_challenge
.
The authorization_endpoint
with its parameters set MUST be opened in the
platform’s default browser or follow the platform’s best practice dealing with
application authorization(s). The redirect_uri
parameter MUST point back to
a location the application can intercept.
All error conditions, both during the authorization phase AND when talking to the API endpoint MUST be handled according to the OAuth specification(s).
Token Endpoint#
The token_endpoint
is used to exchange the authorization code, as obtained
through the redirect_uri
as part of the authorization, for an access and
refresh token. It is also used to retrieve new access tokens when the current
access token expires.
All error conditions, both during the authorization phase AND when talking to the API endpoint MUST be handled according to the OAuth specification(s).
Using the API#
Every API call below will include a cURL example, and an example response that can be expected.
All POST
requests MUST be sent encoded as
application/x-www-form-urlencoded
.
The API can be used with the access token obtained using the OAuth flow as documented above. The following API calls are available:
- Get “Info” from the VPN server, including a list of available profiles
(
/info
); - “Connect” to a VPN profile (
/connect
); - “Disconnect” from a VPN profile (
/disconnect
)
API Calls#
Info#
This call will show the available VPN profiles for this instance. This will allow the application to show the user which profiles are available.
This GET
call has no parameters.
Request#
Request all available VPN profiles:
$ curl \
-H "Authorization: Bearer abcdefgh" \
https://vpn.example.org/vpn-user-portal/api/v4/info
Response#
HTTP/1.1 200 OK
Content-Type: application/json
{
"info": {
"profile_list": [
{
"display_name": {
"en": "Employees",
"nl": "Medewerkers"
},
"profile_id": "employees",
"profile_priority": 0,
"proto_list": [
"wireguard+udp",
"wireguard+tcp"
]
},
{
"display_name": {
"en": "Administrators"
},
"profile_id": "admins",
"profile_priority": 5,
"proto_list": [
"wireguard+udp",
"wireguard+tcp"
]
}
]
}
}
The fields are described in the table below, look under the table for additional information.
TODO: maybe move proto_list
to the “global” /info
object as it is “global” for the whole server…
Key | Always Set | Type | Description | Since | Deprecated |
---|---|---|---|---|---|
profile_priority |
Yes | int |
Hint for client to sorting the available profiles, highest number first | 4.0.0 | No |
profile_id |
Yes | string |
Profile ID | 4.0.0 | No |
display_name |
Yes | object |
Human readable name for profile | 4.0.0 | No |
proto_list |
Yes | []string |
List of supported VPN protocols for profile including transport | 4.0.0 | No |
The display_name
is a JSON object
of the type map[string]string
, i.e. it
contains a mapping from language code to (translated) string.
The profile_priority
field is documented
here.
The proto_list
lists the supported protocols and transports, e.g.:
{
"proto_list": [
"wireguard+udp",
"wireguard+tcp"
]
}
Possible values for proto_list
:
Value | Profile Supports |
---|---|
wireguard+udp |
WireGuard over UDP |
wireguard+tcp |
WireGuard over TCP |
Connect#
Get the profile configuration for the profile you want to connect to.
Request#
Connect to the “Employees” profile (employees
) and specify a WireGuard public
key for when WireGuard will be used:
$ curl \
-d "profile_id=employees" \
--data-urlencode "public_key=nmZ5ExqRpLgJV9yWKlaC7KQ7EAN7eRJ4XBz9eHJPmUU=" \
-H "Authorization: Bearer abcdefgh" \
-H "Accept: application/x-wireguard+udp-profile" \
"https://vpn.example.org/vpn-user-portal/api/v4/connect"
NOTE: a call to /connect
immediately invalidates any previously obtained
VPN configuration files that belong to the same OAuth authorization.
The POST
request has the following parameters:
Parameter | Required | Value(s) |
---|---|---|
profile_id |
Yes | The profile_id , as obtained from the /info response |
public_key |
Yes | A WireGuard public key |
The HTTP Accept
request header can be used to persuade the VPN server to
only return VPN configurations of a particular protocol and/or transport.
See VPN Protocol Selection for more info on how it is determined which VPN configuration file is returned.
Profile ID#
The value of profile_id
MUST be of one of the identifiers for the profiles
returned in the /info
response.
Public Key#
The public_key
parameter MUST be set. The value of public_key
MUST be a
valid WireGuard public key. It has this format:
$ wg genkey | wg pubkey
e4C2dNBB7k/U8KjS+xZdbicbZsqR1BqWIr1l924P3R4=
Response#
You’ll get a WireGuard client configuration with
Content-Type: application/x-wireguard+udp-profile
, e.g.:
Expires: Fri, 06 Aug 2021 03:59:59 GMT
Content-Type: application/x-wireguard+udp-profile
[Interface]
Address = 10.43.43.2/24, fd43::2/64
DNS = 9.9.9.9, 2620:fe::fe
[Peer]
PublicKey = iWAHXts9w9fQVEbA5pVriPlAYMwwEPD5XcVCZDZn1AE=
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = vpn.example:51820
As can be seen in the examples, this API response also contains an Expires
header that indicates when the VPN configuration will expire.
If the client wants to connect over TCP, it can set the Accept
header to
application/x-wireguard+tcp-profile
. If it wants the server to decided, it
can set the Accept
header with both application/x-wireguard+udp-profile
and application/x-wireguard+tcp-profile
set, which is the recommended
strategy.
NOTE: if your Accept
header can not be satisfied by the server, e.g. you
request a TCP configuration, but the server does not support this, you WILL get
an error.
Disconnect#
This call is to indicate to the server that the VPN session(s) belonging to this OAuth authorization can be terminated.
The purpose of this call is to clean up, i.e. release the IP address reserved for the client (WireGuard) and delete the certificate from the list of allowed certificates (OpenVPN).
Request#
$ curl -X POST \
-H "Authorization: Bearer abcdefgh" \
"https://vpn.example.org/vpn-user-portal/api/v4/disconnect"
This POST
call has no parameters.
Response#
HTTP/1.1 204 No Content
Error Responses#
Call | Example Message | Code | Description |
---|---|---|---|
/connect |
no such "profile_id" |
404 | When the profile does not exist, or the user has no permission |
/connect |
invalid value for "profile_id" |
400 | When the syntax for the profile_id is invalid |
/connect |
? |
406 | When the profile does not support the VPN protocol(s) supported by the client (or vice versa) |
TODO: other errors could be invalid public key, invalid Accept
header,
missing parameters, etc.
TODO: maybe when the profile does not exist, a 400 should be returned?
An example:
HTTP/1.1 404 Not Found
Content-Type: application/json
{"error":"no such \"profile_id\""}
In addition to these errors, there can also be an error with the server that we
did not anticipate or is an unusual situation. In that case the response code
will be 500 and the JSON error
key will contain more information about the
error.
VPN Protocol Selection#
The client can influence the VPN transport decision by using the HTTP request
Accept
header.
Value | Description |
---|---|
application/x-wireguard+udp-profile |
Accept WireGuard configurations with UDP transport |
application/x-wireguard+tcp-profile |
Accept WireGuard configurations with TCP transport |
A VPN client that supports both would have this HTTP Accept
request header
value:
Accept: application/x-wireguard+udp-profile,application/x-wireguard+tcp-profile
The client can also claim support for a subset of these protocols/transports by
only explicitly listing the supported protocols/transports in the Accept
header. This makes sure the server never responds with a VPN configuration that
is not supported by the VPN client.
The Accept
request header MUST be set.
Changes from APIv3#
The API was updated to drop a bunch of things no longer needed and simplify the calls. We currently do not describe OpenVPN, but this MAY need to be supported in order to have older servers that still use OpenVPN support APIv4 as well.
/info
calldisplay_name
is always anobject
now/info
call no longer returnsdefault_gateway
(was only needed for OpenVPN+Linux)/info
call no longer returnsvpn_proto_list
(was only needed for OpenVPN+Linux)/info
call no longer returnsdns_search_domain_list
(was only needed for OpenVPN+Linux)/info
callvpn_proto_transport_list
renamed toproto_list
/connect
call now requirespublic_key
/connect
call no longer supportsprefer_tcp
/connect
call now MUST be sent withAccept
header indicating the supported VPN protocol and transport