1.0. Introduction
Geteduroam uses https://discovery.eduroam.app/v1/discovery.json, a JSON file, to list all the institutes generated from CAT. This list is shown in a Geteduroam client so that the user can choose its own institution to connect to.
The script that generates this discovery JSON file can be found on the Geteduroam GitHub. The format of this JSON file is the following:
{
"instances": instances (list, required),
"seq": [SEQUENCE NUMBER] (integer, required); e.g. ISO 8601 date plus two-digit sequential integer 2023020908, meaning: 2023, february 9, update 8),
"version": 1 (integer, required); always set to 1 right now,
}
The "seq" indicates the version of the discovery file and should always increase between updates. A common implementation is the ISO 8601 date (YYYYMMDD) plus two digits for any revision during that day (compare DNS SOA serial). See https://github.com/geteduroam/cattenbak/blob/481e243f22b40e1d8d48ecac2b85705b8cb48494/cattenbak.py#L115 for how it can be implemented in detail. However, clients should only use the seq identifier to distinguish old from new and not strictly parse this as a date.
To protect against rollback attacks, the client MUST check if the sequence number has updated.
Where instances is a list of the form:
{
"cat_idp": cat IdP entityID identifier (integer, required); e.g. 7088,
"country": country code (string, required); e.g. "RO",
"geo": [
"lat": latitude (float, required),
"lon": longitude (float, required),
] (required),
id: cat_id (string, required); e.g. "cat_7088",
name: the name of the organisation to be shown in the UI (string, required); e.g. SURF,
profiles: [
"authorization_endpoint": The authorization endpoint in case OAuth is used (string, optional, default=""); e.g. "https://example.com/oauth/authorize/",
"default": If this profile is the default profile (bool, optional, default=False); e.g. True,
"eapconfig_endpoint": The endpoint to obtain the EAP config (string, required); e.g. "https://example.com/api/eap-config/",
"id": The identifier of the profile (string, required); e.g. "letswifi_cat_1337",
"name": The name of the profile to be shown in the UI (string, required); e.g. "Demo Server",
"redirect": The redirect URI to show ot the user (string, optional, default=""); e.g. "https://example.com/instructions-eduroam",
"oauth": Whether or not OAuth is enabled. If missing, OAuth is not enabled (bool, optional when redirect is present, default=False); e.g. true,
"token_endpoint": The endpoint to get OAuth tokens from (string, optional, default=""); e.g. "https://example.com/oauth/token/",
] (required)
}
This instances list should be parsed by the client. The name of the instance is what is shown in the UI. Filtering on the instance is also done with this name. For example if a user searches for "sur", it would include "SURF" due to substring case-insensitive matching.
2.0. Variants/flows
As can be deduced from the instance format, there are various flows possible to configure the eduroam network with a certain profile:
- Forward the user to a "redirect" page:
id
,name
andredirect
MUST be present - Get the EAP config using tokens obtained through
authorization_endpoint
andtoken_endpoint
using OAuth:eapconfig_endpoint
,authorization_endpoint
,token_endpoint
,name
,id
andoauth
(oauth set to True) MUST be present - Directly get the EAP config from the
eapconfig_endpoint
:eapconfig_endpoint
,name
,id
andoauth
(oauth set to False) MUST be present
Based on the various presences and values of these attributes you can determine the flow as follows:
- If
redirect
is present, then the redirect flow MUST be used - Else, check whether
authorization_endpoint
andtoken_endpoint
are not empty then do the OAuth flow, else direct flow- Note that you can also simply check if
oauth
is set to True. However, checking for the presence of the authorization and token endpoints (and them being not empty) is a more complete check that the current clients also implement
- Note that you can also simply check if
The implementation of each flow will be given later. Before we can do that, however, we first explain how a profile should be selected
3.0. Profile selection
As can be deduced from the JSON format, there are multiple profiles available per instance. If an instance only has one profile then the profile MUST be automatically chosen without any user interaction.
If there are multiple profiles then multiple profiles MUST be shown in the UI, asking for a selection to the user. The profile indicated with the default
attribute set to true SHOULD be in bold, or in case the UI does not support bold text, it SHOULD have a "*" OR "(default)" pre/postfix.
When the profile has been selected, we can use the correct flow to get the EAP metadata. In the next section, we will go over implementing the various flows.
3.3. Flow implementations
This section describes the different way that the app should continue when the profile has been selected.
3.3.1. Redirect
After parsing the discovery entry and determining that the flow is Redirect, the redirect should be verified whether or not the following holds:
- The value is a URL
- The scheme of the URL is HTTPS or HTTP
If the value is not a URL, or the scheme is not HTTP/HTTPS, the app MUST NOT open the URL in the browser but should show a friendly error in the UI that the profile is not available.
If the scheme of the URL is HTTP it MUST be rewritten to HTTPS.
Note that the redirect flow is one of the last steps that the app needs to do as the redirect does not give back an EAP metadata file. This redirect is only used to give the user information on how to proceed with configuring the network himself.
3.3.2. OAuth
The extra fields that are available in the OAuth flow are the authorization_endpoint
and the token_endpoint
. We go over them one by one what should be done.
NOTE: The authorization endpoint and token endpoint docs is taken from https://www.geteduroam.app/developer/api/ and slightly modified
3.3.2.1. Authorization endpoint
Build a URL for the authorization endpoint; take the authorization_endpoint
string from the discovery, and add the following GET parameters (MUST be implemented according to RFC6749 section 4.1.1 for most of these):
response_type
(MUST be set tocode
)code_challenge_method
(MUST be set toS256
)scope
(MUST be set toeap-metadata
)code_challenge
(a code challenge)redirect_uri
(where the user should be redirected after accepting or rejecting your application, GET parameters will added to this URL by the server. MUST be local, e.g. http://127.0.0.1/callback)client_id
(MUST be your client ID as known by the server)state
(a random string that will be set in a GET parameter to the redirect_uri, for you to verify it’s the same flow))
You have created a URL, for example:
https://demo.eduroam.no/authorize.php?response_type=code&code_challenge_method=S256&scope=eap-metadata&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&redirect_uri=http%3A%2F%2Flocalhost%3A1080%2Fcallback&client_id=00000000-0000-0000-0000-000000000000&state=0
You open a local webbrowser to this URL on the users' device and listen on the redirect_uri
for a request to return. Upon receiving a request, the client SHOULD reclaim focus to the application window and MUST handle the request. You may receive these GET parameters:
code
(the authorization code that you can use on the token endpoint)error
(an error message that you can present to the user)state
(the same value as your earlierstate
GET parameter which MUST be checked)
As a reply to this request, you SHOULD simply return a message to the user stating that he should return to the application. Depending on the platform, you SHOULD also return code to trigger a return to the application.
3.3.2.2. Token endpoint
The token endpoint requires a code
, which you obtain via the Authorization endpoint. Use the token_endpoint
string from the discovery.
You need the following POST parameters:
grant_type
(MUST be set toauthorization_code
)code
(MUST be the code received from the authorization endpoint)redirect_uri
(MUST repeat the value used in the previous request)client_id
(MUST repeat the value used in the previous request)code_verifier
(MUST be a code verifier, as documented in the PKCE RFC7636 section 4. This is the preimage of the code challenge to prove that you are the original sender of the authorization endpoint request. )
You get back a JSON dictionary, containing the following keys:
access_token
token_type
(set toBearer
)expires_in
(validity of theaccess_token
in seconds)
Example HTTP conversation
POST /token.php HTTP/1.1
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Content-Length: 209
grant_type=authorization_code&code=v2.local.AAAAAA&redirect_uri=http%3A%2F%2Flocalhost%3A1080%2Fcallback&client_id=00000000-0000-0000-0000-000000000000&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
HTTP/1.1 200 OK
Cache-Control: no-store
Content-Type: application/json;charset=UTF-8
Pragma: no-cache
{
"access_token": "v2.local.AAAAA…==",
"token_type": "Bearer",
"expires_in": 3600
}
Saving this access token SHOULD be done securely, e.g. in a keyring. This way the client can re-use this access token across restarts.
3.3.2.3. Doing the authorized request
Now that the client has retrieved the access token, it needs to get the EAP metadata using it. To do this, the client MUST send the access token in the authorization header when making a request to eapconfig_endpoint
:
curl \
-H "Authorization: Bearer SETTHETOKENHERE" \
https://example.org/api/eap-config
Note that error handling on the HTTP code should done to accordance with RFC6749. In short, when the client gets a HTTP 401 here then that possibly means that the access token is expired or invalid/blacklisted. Therefore the client MUST check before it sends the request if the access token is still valid.
If the 401 is returned, or the client did not even have a non-expired access token in the first place the whole OAuth procedure MUST be redone.
3.3.3. Direct
When the app has determined that the profile does not support redirect and oauth is disabled, the app should get the eap config via the eapconfig_endpoint
. The EAP metadata file is returned in the HTTP response body.
Note that like the URL in redirect, the app MUST parse the eapconfig_endpont
to check whether or not it is a valid URL, the scheme is HTTP or HTTPS and MUST rewrite HTTP to the HTTPS scheme.