Authentication
Version 4 of the Beacon API uses OAuth 2.1 for authentication. If you are using an OAuth library, ensure that it supports OAuth 2.1.
If you are already familiar with OAuth, here are the key details:
Parameter | Value |
---|---|
Authorization URL | https://api.usebeacon.app/v4/login |
Token URL | https://api.usebeacon.app/v4/login |
Device URL | https://api.usebeacon.app/v4/device |
Starting A Login Request From A Browser
To request a login, generate two random values: A state
and a code_verifier.
Store these values temporarily in the user’s browser. A session cookie or session storage is best for this purpose. After the login is complete, the state will be relayed back, which you should verify before continuing. This protects against cross-site request forgery. The code verifier will defend against man-in-the-middle attacks, which may occur if the user’s router or other security parameters have been tampered with. See the “Challenge” section below for more details.
Make a GET request to the authorization url, which will redirect the user to the login interface. The following query parameters must be included in this request:
Parameter | Value |
---|---|
client_id | Your application UUID. |
scope | This is a space-separated list of the requested scopes. Your application must be registered for each requested scope, though not all registered scopes must be requested. The most common value is common users:read . See Scopes for the full list of supported scopes. |
redirect_uri | One of the callback URLs registered for your application. |
state | A random state value generated by a cryptographically secure psuedorandom number generator. |
response_type | Always code . |
code_challenge | See Code Challenge. |
code_challenge_method | Always S256 . |
device_id | Optional. If your application has a unique device identifier, pass it in this parameter. If the user has two-step authentication enabled and chooses to trust the device, the verification request will be skipped on subsequent logins. |
no_redirect | Optional. If true , the login url will be returned in a JSON object with a login_url key and an HTTP 200 status. Otherwise, the normal 302 redirect behavior will be used instead. |
Sample Code
Raw HTTP
GET /v4/login?state=0fc732a9-05d8-403c-bdce-231c90f7b924&client_id=413d18e9-790c-4477-bcbe-ae31cf1227f0&scope=common%20users%3Aread&redirect_uri=https%3A%2F%2Fjiinuko.edu%2Fehreg%2Foauth&response_type=code&code_challenge=2b6-gW15O10gZcp97PaXVmmu_4IrMXVBXNWtP8q8crs&code_challenge_method=S256 HTTP/1.1
Host: api.usebeacon.app
JavaScript
const params = new URLSearchParams();
params.append('state', '0fc732a9-05d8-403c-bdce-231c90f7b924');
params.append('client_id', '413d18e9-790c-4477-bcbe-ae31cf1227f0');
params.append('scope', 'common users:read');
params.append('redirect_uri', 'https://jiinuko.edu/ehreg/oauth');
params.append('response_type', 'code');
params.append('code_challenge', '2b6-gW15O10gZcp97PaXVmmu_4IrMXVBXNWtP8q8crs');
params.append('code_challenge_method', 'S256');
fetch(`https://api.usebeacon.app/v4/login?${params.toString()}`, {
"method": "GET",
"headers": {}
})
.then((res) => res.text())
.then(console.log.bind(console))
.catch(console.error.bind(console));
Python
try:
response = requests.get(
url="https://api.usebeacon.app/v4/login",
params={
"state": "0fc732a9-05d8-403c-bdce-231c90f7b924",
"client_id": "413d18e9-790c-4477-bcbe-ae31cf1227f0",
"scope": "common users:read",
"redirect_uri": "https://jiinuko.edu/ehreg/oauth",
"response_type": "code",
"code_challenge": "2b6-gW15O10gZcp97PaXVmmu_4IrMXVBXNWtP8q8crs",
"code_challenge_method": "S256",
},
)
print('Response HTTP Status Code: {status_code}'.format(
status_code=response.status_code))
print('Response HTTP Response Body: {content}'.format(
content=response.content))
except requests.exceptions.RequestException:
print('HTTP Request failed')
Sample Response
HTTP/1.1 302 Found
Location: https://usebeacon.app/account/login
Completing A Browser Login Request
Once the user has completed their login, the user will be redirected back to your redirect_uri
with some additional query parameters.
Parameter | Purpose |
---|---|
state | The state value sent when starting the login request. If this does not match the value stored in the user’s browser, do not continue. |
code | Used to request an access token from the Beacon API. |
Use these parameters to build a JSON object and POST it to https://api.usebeacon.app/v4/login
.
Key | Value |
---|---|
client_id | Your application UUID. |
client_secret | If your application is a confidential client, include its secret. Otherwise, omit this key. |
code | The code described above. |
grant_type | Always authorization_code . |
redirect_uri | The same redirect_uri used when starting the login request. |
code_verifier | The raw (no hashing, no encoding) code_verifier sent when starting the login request. |
device_id | Like the initial request, include the device id if one exists. |
The API will respond with authorization information in a JSON object. The value in accessToken
is used with the rest of the API in the form of an Authorization: Bearer <accessToken>
header. If making API requests from within the browser, CORS policy will not include the Authorization
header, so use the X-Beacon-Token: <accessToken>
header instead.
Sample Code
Raw HTTP
POST /v4/login HTTP/1.1
Content-Type: application/json
Host: api.usebeacon.app
{
"client_id": "413d18e9-790c-4477-bcbe-ae31cf1227f0",
"code": "oBOrJr-MHnAQfiNTYzxgT4CIPcS4_1ygVplTBcAhZS0",
"grant_type": "authorization_code",
"redirect_uri": "https://jiinuko.edu/ehreg/oauth",
"code_verifier": "0RRGb4Mid9Fj1YXX17z_Rtkh0XQZX5KBvmr0wNoDqYU"
}
JavaScript
fetch("https://api.usebeacon.app/v4/login", {
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify({
client_id: '413d18e9-790c-4477-bcbe-ae31cf1227f0',
code: 'oBOrJr-MHnAQfiNTYzxgT4CIPcS4_1ygVplTBcAhZS0',
grant_type: 'authorization_code',
redirect_uri: 'https://jiinuko.edu/ehreg/oauth',
code_verifier: '0RRGb4Mid9Fj1YXX17z_Rtkh0XQZX5KBvmr0wNoDqYU'
})
})
.then((res) => res.text())
.then(console.log.bind(console))
.catch(console.error.bind(console));
Python
try:
response = requests.post(
url="https://api.usebeacon.app/v4/login",
headers={
"Content-Type": "application/json",
},
data=json.dumps({
"client_id": "413d18e9-790c-4477-bcbe-ae31cf1227f0",
"code": "oBOrJr-MHnAQfiNTYzxgT4CIPcS4_1ygVplTBcAhZS0",
"redirect_uri": "https://jiinuko.edu/ehreg/oauth",
"code_verifier": "0RRGb4Mid9Fj1YXX17z_Rtkh0XQZX5KBvmr0wNoDqYU",
"grant_type": "authorization_code"
})
)
print('Response HTTP Status Code: {status_code}'.format(
status_code=response.status_code))
print('Response HTTP Response Body: {content}'.format(
content=response.content))
except requests.exceptions.RequestException:
print('HTTP Request failed')
Sample Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"token_type": "Bearer",
"access_token": "kyXwY-jBHUONral73ctZi0dAtL8VN3aJGemhkDDH9y4",
"refresh_token": "3MBaAT6f3Nvc9XeF62SDUjJ-nqKNkJRV8hTJpPwQ5kA",
"access_token_expiration": 1748835444,
"refresh_token_expiration": 1751423844,
"access_token_expires_in": 3600.120197057724,
"refresh_token_expires_in": 2592000.1201970577,
"scope": "common users:read",
"now": 1748831843.879803
}
The API will respond with authorization information in a JSON object. The value in access_token
is used with the rest of the API in the form of an Authorization: Bearer <access_token>
header. If making API requests from within the browser, CORS policy will not include the Authorization
header, so use the X-Beacon-Token: <access_token>
header instead.
See Token Expiration for handling of token expiration.
Starting A Device Login Request
In some cases, you may need to start a login request when a browser is unavailable or impractical. Examples of when you might use a device login include a console, TV, or Discord bot. You may have experienced this before when an app on a smart TV shows a code and a URL. You would then visit the URL, enter the code, and complete the login process. The Beacon API supports this technique as well.
To obtain a code and URL, make a POST
request to the device url above. The body of the request should be url-encoded form data with the following parameters.
Parameter | Notes |
---|---|
client_id | Your application UUID. |
client_secret | If your application is a confident client, the secret must be included. |
scope | This is a space-separated list of the requested scopes. Your application must be registered for each requested scope, though not all registered scopes must be requested. The most common value is common users:read . See Scopes for the full list of supported scopes. |
code_challenge | See Code Challenge. |
code_challenge_method | Always S256 . |
Sample Code
Raw HTTP
POST /v4/device HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: api.usebeacon.app
client_id=413d18e9-790c-4477-bcbe-ae31cf1227f0&scope=common%20users%3Aread&code_challenge=2b6-gW15O10gZcp97PaXVmmu_4IrMXVBXNWtP8q8crs&code_challenge_method=S256
JavaScript
const params = new URLSearchParams();
params.append('client_id', '413d18e9-790c-4477-bcbe-ae31cf1227f0');
params.append('scope', 'common users:read');
params.append('code_challenge', '2b6-gW15O10gZcp97PaXVmmu_4IrMXVBXNWtP8q8crs');
params.append('code_challenge_method', 'S256');
fetch("https://api.usebeacon.app/v4/device", {
"method": "POST",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
},
"body": params.toString()
})
.then((res) => res.text())
.then(console.log.bind(console))
.catch(console.error.bind(console));
Python
try:
response = requests.post(
url="https://api.usebeacon.app/v4/device",
headers={
"Content-Type": "application/x-www-form-urlencoded",
},
data={
"client_id": "413d18e9-790c-4477-bcbe-ae31cf1227f0",
"scope": "common users:read",
"code_challenge": "2b6-gW15O10gZcp97PaXVmmu_4IrMXVBXNWtP8q8crs",
"code_challenge_method": "S256"
},
)
print('Response HTTP Status Code: {status_code}'.format(
status_code=response.status_code))
print('Response HTTP Response Body: {content}'.format(
content=response.content))
except requests.exceptions.RequestException:
print('HTTP Request failed')
Sample Response
HTTP/1.1 201 Created
Content-Type: application/json
{
"device_code": "4a196833-595c-53ea-b72f-59463f92c3cf",
"user_code": "C73J-3NTJ",
"verification_uri": "https://usebeacon.app/device",
"verification_uri_complete": "https://usebeacon.app/device?code=C73J-3NTJ",
"interval": 5,
"expires_in": 599.99588
}
Key | Notes |
---|---|
device_code | A UUID for this login request. |
user_code | This is the code to show the user to enter during the login process. |
verification_uri | This is the address the user should visit to enter the user_code . |
verification_uri_complete | This address is has the user_code completed, making it useful for showing as a QR code. |
interval | Check the token url no more than once every interval seconds. |
expires_in | Number of seconds until the login request expires. |
Completing A Device Login Request
After creating the device login request, poll the token url every interval
seconds until the request is completed, has expired, or has been rejected. This request is a POST
request with a body that is either a url-encoded query string or a JSON object.
Key | Notes |
---|---|
client_id | Your application UUID. |
client_secret | If your application is a confident client, the secret must be included. |
device_code | The UUID found in the device_code key when the device login request was created. |
grant_type | Always device_code or urn:ietf:params:oauth:grant-type:device_code . They are functionally identical, but the more verbose value is more commonly used. |
code_verifier | The raw (no hashing, no encoding) code_verifier sent when starting the login request. |
Sample Code
Raw HTTP
POST /v4/login HTTP/1.1
Content-Type: application/json
Host: api.usebeacon.app
{
"client_id": "413d18e9-790c-4477-bcbe-ae31cf1227f0",
"device_code": "4a196833-595c-53ea-b72f-59463f92c3cf",
"grant_type": "device_code",
"code_verifier": "0RRGb4Mid9Fj1YXX17z_Rtkh0XQZX5KBvmr0wNoDqYU"
}
JavaScript
fetch("https://api.usebeacon.app/v4/login", {
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify({
"client_id": "413d18e9-790c-4477-bcbe-ae31cf1227f0",
"device_code": "4a196833-595c-53ea-b72f-59463f92c3cf",
"grant_type": "device_code",
"code_verifier": "0RRGb4Mid9Fj1YXX17z_Rtkh0XQZX5KBvmr0wNoDqYU"
})
})
.then((res) => res.text())
.then(console.log.bind(console))
.catch(console.error.bind(console));
Python
try:
response = requests.post(
url="https://api.usebeacon.app/v4/login",
headers={
"Content-Type": "application/json",
},
data=json.dumps({
"client_id": "413d18e9-790c-4477-bcbe-ae31cf1227f0",
"device_code": "4a196833-595c-53ea-b72f-59463f92c3cf",
"grant_type": "device_code",
"code_verifier": "0RRGb4Mid9Fj1YXX17z_Rtkh0XQZX5KBvmr0wNoDqYU"
})
)
print('Response HTTP Status Code: {status_code}'.format(
status_code=response.status_code))
print('Response HTTP Response Body: {content}'.format(
content=response.content))
except requests.exceptions.RequestException:
print('HTTP Request failed')
Sample Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"token_type": "Bearer",
"access_token": "kyXwY-jBHUONral73ctZi0dAtL8VN3aJGemhkDDH9y4",
"refresh_token": "3MBaAT6f3Nvc9XeF62SDUjJ-nqKNkJRV8hTJpPwQ5kA",
"access_token_expiration": 1748835444,
"refresh_token_expiration": 1751423844,
"access_token_expires_in": 3600.120197057724,
"refresh_token_expires_in": 2592000.1201970577,
"scope": "common users:read",
"now": 1748831843.879803
}
The API will respond with authorization information in a JSON object. The value in access_token
is used with the rest of the API in the form of an Authorization: Bearer <access_token>
header. If making API requests from within the browser, CORS policy will not include the Authorization
header, so use the X-Beacon-Token: <access_token>
header instead.
See Token Expiration for handling of token expiration.
Token Expiration
The access token will expire quickly. At the time of this writing, the expiration is 1 hour. The refresh_token
key can be used to request a new access token. Utilize the access_token_expiration
and refresh_token_expiration
keys to know when a refresh is necessary. If the user is making a request, and the access token has expired, refresh it first.
Consider implementing a timed function to look for refresh tokens that are expiring in the near future and refresh them. This will prevent users from having to log in again after an absence. At the time of this writing, refresh tokens expire after 30 days.
Each refresh will replace both the access token and refresh token.
To refresh a token, POST
a JSON object to the token url. This object looks much like the access token request:
Key | Value |
---|---|
client_id | Your application UUID. |
client_secret | If your application is a confidential client, include its secret. Otherwise, omit this key. |
grant_type | Always refresh_token . |
refresh_token | The refresh token. |
scope | Space-separated list of scopes. This must match the originally requested scopes. |
The response will match that of the access token response.
Sign Out
To terminate an access token, make a DELETE
request to https://api.usebeacon.app/v4/sessions/{accessToken}
. The server will respond with a 204 status if successful.
Sample Code
Raw HTTP
DELETE /v4/sessions/kyXwY-jBHUONral73ctZi0dAtL8VN3aJGemhkDDH9y4 HTTP/1.1
Host: api.usebeacon.app
JavaScript
fetch("https://api.usebeacon.app/v4/sessions/kyXwY-jBHUONral73ctZi0dAtL8VN3aJGemhkDDH9y4", {
"method": "DELETE",
"headers": {}
})
.then((res) => res.text())
.then(console.log.bind(console))
.catch(console.error.bind(console));
Python
try:
response = requests.delete(
url="https://api.usebeacon.app/v4/sessions/kyXwY-jBHUONral73ctZi0dAtL8VN3aJGemhkDDH9y4",
)
print('Response HTTP Status Code: {status_code}'.format(
status_code=response.status_code))
print('Response HTTP Response Body: {content}'.format(
content=response.content))
except requests.exceptions.RequestException:
print('HTTP Request failed')
Sample Response
HTTP/1.1 204 No Content
Scopes
The Beacon API supports the following scopes:
Scope | Purpose |
---|---|
common | This scope is always included. Provides access to the most common features, such as game information. |
users:read | Allows reading information about the authenticated user and other users. |
users:update | Allows editing of the user. This is not necessary for working with user project or cloud files. |
users.private_key:read | Allows fetching the user’s private key, which is necessary for reading encrypted portions of projects and all cloud files. |
sentinel:read | Allows reading the user’s Sentinel data. |
sentinel:write | Allows full editing of the user’s Sentinel data, including adding or removing servers, groups, bans, scripts. |
Code Challenge
A code_verifier
must first be generated to produce a challenge. The code_verifier
must be between 43 and 128 characters long and can only contain alphanumeric characters or the special characters -
, .
, _
, or ~
.
We can generate the code_challenge
with the code_verifier
. This is a Base64URL encoded SHA-256 hash, not regular Base64. In pseudo-code, it might look like this:
code_challenge = BASE64URL(SHA256(code_verifier))
Some languages automatically hex-encode their hashes. Make sure you are encoding the raw hash and not the hex-encoded hash.