This tutorial provides guidance for implementing Riot Sign On (RSO) from initially connecting the service, serving a Sign In link, authenticating the user, to processing the user’s tokens.
To start working with RSO, you will need the following:
Follow the instructions on the RSO Client Request Form to submit a request for an RSO client. Older clients may continue to use client secret basic and receive a client_secret, but newer clients will receive a few fields to support private key JWT client auth, such as:
Note: The Private key that is generated on your behalf is not stored and cannot be retrieved. New keypairs can be generated without disabling the old keypair.
If you are familiar with JWT signing, you may choose to implement proper token minting using the returned private key for increased security.
If you are not familiar with JWTs and signing, continue to use the example 100 year token as your client assertion in the code examples in this process, but be sure to store and treat it as you would a plaintext password.
Note: If you want locale-specific values for client name, logo uri, privacy policy uri, and terms of service, after your client is created, please respond with the list using the following form: <field>#<locale>, for example, privacy policy url#de_DE: <url>
Use the following Riot-supported locales: cs_CZ, de_DE, el_GR, en_AU, en_GB, en_PL, en_US, es_AR, es_ES, es_MX, fr_FR, hu_HU, it_IT, ja_JP, pl_PL, pt_BR, ro_RO, ru_RU, tr_TR.
The Authorization Code flow consists of the following:
var express = require('express'),
app = express();
app.get('/', function(req, res) {
res.send("index");
});
app.get('/oauth2-callback', function(req, res) {
res.send("callback");
});
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
The purpose of route / is to deliver a Sign In link that the player can click to authenticate against Riot Sign On. Riot Sign On expects several parameters passed to it or it will throw an error indicating something not included. Make sure the following fields are included:
Using all the mandatory fields, the Sign In link is:
https://auth.riotgames.com/authorize?redirect_uri=http://local.leagueoflegends.com:3000/oauth2-callback&client_id=oujzg5jiibvzo&response_type=code&scope=openid
Modifying the code to make the Sign In link a bit more portable, the server.js now looks as follows:
. . .
var appBaseUrl = "http://local.example.com:3000",
appCallbackUrl = appBaseUrl + "/oauth2-callback";
var provider = "https://auth.riotgames.com",
authorizeUrl = provider + "/authorize";
var clientID = "oujzg5jiibvzo";
app.get('/', function(req, res) {
var link = authorizeUrl
+ "?redirect_uri=" + appCallbackUrl
+ "&client_id=" + clientID
+ "&response_type=code"
+ "&scope=openid";
// create a single link, send as an html document
res.send('<a href="' + link + '">Sign In</a>');
});
. . .
Here, the player is presented with the Login Page of Riot Sign On, and may sign in.
When the player successfully logs in, a 302 Redirect sends their browser to the redirect_uri that was included in the Sign In link.
Note: The callback route http://local.example.com:3000/oauth2-callback does not do anything yet.
This route receives a code as a url query-string parameter, and the server must then make a server-to-server request to exchange this code for Access, Identity, and Refresh Tokens. You must send the following to Riot Sign On’s Token endpoint to receive these Tokens back:
To simplify making requests, use the request package. The basic callback route using private key JWT, looks as follows:
. . .
var request = require('request');
var clientAssertion = "your-signed-jwt-here";
var appBaseUrl = "http://local.example.com:3000",
appCallbackUrl = appBaseUrl + "/oauth2-callback";
var provider = "https://auth.riotgames.com",
authorizeUrl = provider + "/authorize",
tokenUrl = provider + "/token";
. . .
app.get('/oauth2-callback', function(req, res) {
var accessCode = req.query.code;
// make server-to-server request to token endpoint
// exchange authorization code for tokens
request.post({
url: tokenUrl,
form: { // post information as x-www-form-urlencoded
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: clientAssertion,
grant_type: "authorization_code",
code: accessCode, // accessCode should be url decoded before being set here
redirect_uri: appCallbackUrl
}
}, function (error, response, body) {
// do something with the response?
});
});
The following example uses Client Secret Basic:
. . .
var request = require('request');
var clientID = "your-client-id-here",
clientSecret = "your-client-secret-here";
var appBaseUrl = "http://local.example.com:3000",
appCallbackUrl = appBaseUrl + "/oauth2-callback";
var provider = "https://auth.riotgames.com",
authorizeUrl = provider + "/authorize",
tokenUrl = provider + "/token";
. . .
app.get('/oauth2-callback', function(req, res) {
var accessCode = req.query.code;
// make server-to-server request to token endpoint
// exchange authorization code for tokens
request.post({
url: tokenUrl,
auth: { // sets "Authorization: Basic ..." header
user: clientID,
pass: clientSecret
},
form: { // post information as x-www-form-urlencoded
grant_type: "authorization_code",
code: accessCode, // accessCode should be url decoded before being set here
redirect_uri: appCallbackUrl
}
}, function (error, response, body) {
// do something with the response?
});
});
A raw Token endpoint response looks similar to:
{
"scope":"openid",
"expires_in":600,
"token_type":"Bearer",
"refresh_token":"dXJuOnJpb3Q6cOk1qdNal ... 8zN3NzbQ.xw96rZeGEMtrFlDCGLyA",
"id_token":"eyJhbGciJSUzI1mtpZCInMxIn0 ... YiI6InVybjpyaW90OpZDp2MTpNalV",
"sub_sid":"vldfsXGdDPoafSKfjS932cslKu8JDUKZ-woZvXDoq8",
"access_token":"eyJhbGciOi1NsImZCI6InM ... NTkzMTA3LCJjaWQiJnmE-BVnZbYqY"
}
Change the callback of the response to parse this and get the Tokens.
request.post({
. . .
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
// parse the response to JSON
var payload = JSON.parse(body);
// separate the tokens from the entire response body
var tokens = {
refresh_token: payload.refresh_token,
id_token: payload.id_token,
access_token: payload.access_token
};
// legibly print out our tokens
res.send("<pre>" + JSON.stringify(tokens, false, 4) + "</pre>");
} else {
res.send("/token request failed");
}
});
The following is an explanation of the fields in the response:
The Access and ID Tokens are received as the final step of a grant. Access Tokens are used for scoped authentication of a client and player to a resource, while ID Tokens provide information necessary to authenticate a player’s identity. ID Tokens are usually set as a cookie in the user’s browser to establish identity between pages and services.
Riot Sign On Access and ID Tokens are encoded as Signed JSON Web Tokens (JWT) to prevent tampering. Additionally, Access Tokens are encrypted and cannot be decoded.
Refresh Tokens are used to obtain a new Access Token when a given Access Token has expired, and have a much longer lifespan than an Access Token. The expiration flow of Access Tokens ensures that even if one is compromised, it has a very limited life-span.
IMPORTANT
It is important to note that Access Tokens are encrypted and cannot be decoded or verified.
Once complete, a full example provides the following:
You can now send a player to Riot to authenticate, receive the auth code returned. and exchange it for Access, Identity, and Refresh tokens.
The Refresh Token is issued for the purpose of obtaining new Access Tokens when an older one expires. RSO Refresh tokens are self-contained, signed JSON Web Tokens (JWT) that can be inspected and validated locally.
Category | Description |
Format | JWT signed |
Refreshable? | N/A |
Usage | Obtain a new Access Token |
Visibility To Javascript | No |
Visibility To User | No |
Visibility To Server | Yes |
Example encrypted token:
dXJuOnJpb3Q6cGlkOnYxOk1qVXdNalE2UkVWV09R.Z2pyamNvaG8zN3NzbQ.xw96rZeGEmeMtrFlDCGLyA
Refresh Tokens are only used to obtain a new Access Token, usually when the previous one has expired due to time expiration or revocation.
Headers
Authorization: Basic Z2pyamNvaG8NzbTpPLWpTb ... tkcEN6amp13U2ZTOWpjU0w=
POST-Data
grant_type: refresh_token
refresh_token: dXJuOnJpb3Q6cGlknYxO ... G8zN3NzbQ.xw96rZEmeMtrFlDCGLyA
(optional) scope: [ same or narrower scope ]
URI
https://auth.riotgames.com/token
Response:
{
"scope":"openid",
"expires_in":600,
"token_type":"Bearer",
"refresh_token":"dXJuOnJpb3Q6cGlkOn ... amNvaG8zN3NzbQeGEmeMtrFlDCGLyA",
"access_token":"eyJhbGciOiJSUzI1NiI ... sFwkadLmWmwtvJouhX22Tc6vPnfXTk"
}
When using a Refresh Token, there are two actions RSO may take. If it replies with a new Refresh Token, it must be used in all future Access Token refreshes and the previous Refresh Token is now invalid.
If the same Refresh Token is received in the response, you may continue to use it in future refresh requests.
The UserInfo endpoint is a protected RSO endpoint where client applications can retrieve consented claims, or assertions, about the logged in player.
The claims are typically packaged in a JSON object where the sub member denotes the subject (player) identifier.
This is the first endpoint established to support Bearer access tokens from Implementing Authorization Codes on the Web.
Headers
Authorization: Bearer eyJhbGciOiJSUzI ... axZMUW2WchZU9fGHM_h61A
URI
https://auth.riotgames.com/userinfo
Response
{
"sub": "2SqiN9ZWecDMZdo-y3Xaaoos32kTazZQDgzOyxzJe66SzGYqIljuuxjmctK0-XbBIhzZn929LZnH5S90L4vNVjx7En27”,
"cpid": "NA1"
}