Skip to main content

Integration guide for OpenID Connect authentication

Overview

Sky Mavis's OAuth 2.0 APIs can be used for both authentication and authorization. This page describes our OAuth 2.0 implementation for authentication, which adheres to the OpenID Connect (OIDC) specification. By using the Sky Mavis implementation of OIDC, you can delegate sign-up, sign-in, and other identity management activities in your app to Sky Mavis Account. The instructions in the document demonstrate how to do so regardless of the programming language, by sending and receiving HTTP requests.

Prerequisites

  • API key and access to the Sky Mavis Account service. For more information, see Get started.
  • A redirect URI.
  • Branding information that your users see when signing in.

For more information, see Get started.

API endpoints

The following table lists the API endpoints used throughout the OIDC authentication flows.

Endpoint nameURL
Issuerhttps://athena.skymavis.com
Authorization endpointhttps://api-gateway.skymavis.com/oauth2/auth
Token endpointhttps://api-gateway.skymavis.com/account/oauth2/token
JWKS URIhttps://api-gateway.skymavis.com/account/.well-known/jwks.json
User info endpointhttps://api-gateway.skymavis.com/account/userinfo
Revocation endpointhttps://api-gateway.skymavis.com/account/oauth2/revoke
End session endpointhttps://api-gateway.skymavis.com/account/oauth2/sessions/logout

The authorization endpoint is used by the client to obtain an authorization grant from the resource owner.

Authentication

To authenticate requests to the OAuth 2.0 APIs, you can send your API key in two ways:

  • In the X-API-Key request header:

    POST /account/oauth2/token
    Host: https://api-gateway.skymavis.com
    Content-Type: application/x-www-form-urlencoded
    X-API-Key: abcdef12345

    ...
  • In the query string:

    POST /account/oauth2/token?apikey=abcdef12345

Authenticate the user

Authenticating a user involves obtaining an ID token and validating it. ID tokens are a standardized feature of OIDC designed for use in sharing identity assertions on the internet.

The most commonly used flows for authenticating a user and obtaining an ID token are called "authorization code flow" and "implicit flow." The authorization code flow allows the backend server of an app to verify the identity of the person using a browser or mobile device. The implicit flow is used when a client-side app (typically a JavaScript app running in the browser) needs to access APIs directly instead of via its backend server.

The following example describes how to perform the authorization code flow for authenticating the user. The implicit flow is significantly more complicated because of security risks in handling and using tokens on the client side. If you need to implement an implicit flow, read about it in the Implicit flow section.

Sky Mavis Account also allows an app to authenticate the user by signing in with Ronin through the Resource Owner Password Credentials (ROPC) flow.

Have you set up the grant type?

Sky Mavis Account validates app requests with the grant type configured on the OAuth 2.0 page in the Developer Console. Make sure you select the grant types that suit your app's usage.

When a user tries to sign in with a Sky Mavis account, you need to do the following:

  1. Send an authentication request
  2. Get an access token and ID token
  3. Get user information from the ID token
  4. Authenticate the user

1. Send an authentication request

Create an HTTPS GET request with the appropriate URL parameters. Note the use of HTTPS rather than HTTP in all the steps of this process, because HTTP connections are refused. Retrieve the base URL from the discovery document using the authorization_endpoint metadata value. This section assumes the base URL is https://api-gateway.skymavis.com/oauth2/auth.

For a basic request, specify the following parameters:

ParameterRequiredDescription
client_idRequiredThe client ID obtained from the Developer Console.
redirect_uriRequiredThe HTTP endpoint on your server that receives the response from Sky Mavis. The value must exactly match the authorized redirect URI for the given client_id specified in the Developer Console. If this value doesn't match an authorized URI, the request fails with a redirect_uri_mismatch error.
response_typeRequiredThe response type must be set to the code value in the authorization code flow.
scopeRequiredThe scope must be set to the openid value.
nonceRequiredA random value generated by your app that enables replay protection.
stateOptionalAn opaque value used to maintain state between the request and the callback. For more information, see Authorization code flow.
login_hintOptionalWhen your app knows which user it is trying to authenticate, it can provide this parameter as a hint to the authentication server. Passing this hint suppresses the account chooser and either pre-fills the email box on the sign-in form, or selects the proper session. value can be either an email address or the sub string, which is equivalent to the user's Mavis ID. If you do not provide a login_hint and the user is currently logged in, the consent screen includes a request for approval to release the user's email address to your app.

Here's an example request with the parameters described:

HTTP
GET /oauth2/auth?
Host: https://api-gateway.skymavis.com

client_id={client_id}
&redirect_uri={redirect_uri}
&response_type=code
&state=random_string
&scope=openid

2. Get an access token and ID token

The response includes a code parameter, a one-time authorization code that your server can exchange for an access token and ID token. Your server makes this exchange by sending an HTTPS POST request. The POST request is sent to the token endpoint, which you can retrieve from the Discovery document using the token_endpoint metadata value. This section assumes the endpoint is https://api-gateway.skymavis.com/oauth2/auth. The request must include the following parameters:

ParameterRequiredDescription
codeRequiredThe authorization code that is returned from the authentication request.
client_idRequiredThe client ID obtained from the Developer Console.
client_secretRequiredThe client secret obtained from the Developer Console.
redirect_uriRequiredAn authorized redirect URI for the given client_id specified in the Developer Console. If this value doesn't match an authorized URI, the request fails with a redirect_uri_mismatch error.
grant_typeRequiredThis field must contain the value of authorization_code defined in the Developer Console.

Here's an example request with the parameters described:

HTTP
POST /account/oauth2/token
Host: https://api-gateway.skymavis.com
Content-Type: application/x-www-form-urlencoded
X-API-Key: {your_api_key}

grant_type=authorization_code
&code={authorization_code}
&redirect_uri={your_callback_url}
&client_id={your-client-id}
&client_secret={your-client-secret}

Successful response

A successful response to this request contains the following parameters in a JSON array:

ParameterDescription
access_tokenA token that can be sent to a Sky Mavis API.
expires_inThe remaining lifetime of the access token in seconds.
id_tokenA JSON Web Token (JWT) that contains identity information about the user that is digitally signed by Sky Mavis.
scopeThe scopes of access granted by the access_token expressed as a list of space-delimited, case-sensitive strings.
token_typeIdentifies the type of token returned. This field always has the value Bearer.

3. Get user information from the ID token

An ID token is a JSON Web Token (JWT)—a cryptographically signed Base64-encoded JSON object. Normally, you'd have to validate a token before using it, but because you're communicating directly with Sky Mavis over an intermediary-free HTTPS channel and are using your client secret to authenticate yourself to Sky Mavis, you can be confident that the token you receive really comes from Sky Mavis and is valid. If your server passes the ID token to other components of your app, then the other components must validate the token before using it.

Given that most API libraries combine the validation with the work of decoding the base64url-encoded values and parsing the JSON within, you might end up validating the token anyway as you access the claims in the ID token.

An ID token is a JSON object containing a set of name-value pairs. Here's an example, formatted for readability:

JSON
{
"acr": "google",
"amr": [
"oidc"
],
"at_hash": "7Y1j0a-pn_RQXEW4vsLkHw",
"aud": [
"{client_id}"
],
"auth_time": 1683283201,
"email": "test@skymavis.com",
"exp": 1683888020,
"iat": 1683283220,
"iss": "https://athena.skymavis.com/",
"jti": "06934da0-03cc-483b-9d78-eb7ff143aec9",
"name": "test user",
"nonce": "1683283184931",
"rat": 1683283185,
"redirect_uri": "http//localhost/callback",
"roninAddress": "0x000000",
"sid": "5c4feba2-02ec-49cd-9d6c-257b29c57e2f",
"sub": "{mavis_id}",
"walletConnect": null
}

4. Authenticate the user

After obtaining user information from the ID token, you need to query your app's user database. If the user already exists in the database, start an app session for that user if all login requirements are met by the Sky Mavis API response.

If the user does not exist in your user database, then redirect the user to your new-user sign-in flow. You may be able to auto-register the user based on the information you receive from Sky Mavis, or at the very least you may be able to pre-populate many of the fields that you require on your registration form. In addition to the information in the ID token, you can get additional user profile information at the user info endpoint https://api-gateway.skymavis.com/account/userinfo.

Example request:

HTTP
GET /account/userinfo
Host: https://api-gateway.skymavis.com
X-API-Key: {your_api_key}
Authorization: Bearer {access_token}

Log out the user

In OAuth 2.0, the logout process refers to terminating the user's session and revoking the app's access to the user's resources. Logging out is performed on both the client app and the identity provider.

For a logout request, specify the following parameters:

ParameterRequiredDescription
apikeyRequiredThe API key obtained from the Developer Console.
id_token_hintRequiredA hint about the user's current ID token, helping the server identify the correct session. Replace id_token_issued_to_client with the actual ID token.
post_logout_redirect_uriRequiredA URL for redirecting the user's browser after logout. The server validates this URL against pre-registered URLs. Replace post_logout_redirect_uri with your app's URI.
stateRequiredMaintains state between the logout request and callback. It is a random string generated by the client app for security.

Here's an example request with the parameters described:

GET /account/oauth2/sessions/logout?
Host: https://api-gateway.skymavis.com

apikey={apikey}
&id_token_hint={id_token_issued_to_client}
&post_logout_redirect_uri={post_logout_redirect_uri}
&state=random_string

Advanced topics

Authorization code flow

The authorization code flow returns an authorization code to the client, which can then exchange it for an access token and ID token directly.

Token endpoint

When using the authorization code flow, all tokens are returned by the token endpoint (https://api-gateway.skymavis.com/account/oauth2/token). This is an HTTP endpoint that Sky Mavis clients can use to obtain an access token, an ID token, and optionally a refresh token. The client sends a token request to the token endpoint to obtain a token response when using the authorization code flow.

For a token request, specify the following parameters:

ParameterRequiredDescription
client_idRequiredThe client ID obtained from the Developer Console.
redirect_uriRequiredAn authorized redirect URI for the given client_id specified in the Developer Console. If this value doesn't match an authorized URI, the request fails with a redirect_uri_mismatch error.
response_typeRequiredDetermines whether a code, id_token token or just id_token is returned.
scopeRequiredThe scope value, whether offline_access, offline, or openid.
id_token_hintOptionalValid id_token (of an existing session) used to identity the subject. Should be provided when using the prompt none.
login_hintOptionalA valid login name of a user that is used for username inputs or preselecting a user on select_account. Be sure to encode the hint correctly using URL encoding (especially when using "+" or alike in the login name).
max_ageOptionalSeconds since the last active successful authentication of the user.
nonceOptionalA random string value to associate the client session with the ID token and for replay attacks mitigation. Must be provided when using the implicit flow.
promptOptionalIf the auth server prompts the user for authentication or reauthentication:
  • No prompt: The user has to choose a session if more than one session exists.
  • none: The user must be authenticated without interaction, an error is returned otherwise.
  • login: The user must reauthenticate and provide a user name.
  • select_account: The user is prompted to select one of the existing sessions or create a new one.
  • create: The registration form is displayed to the user directly.
stateOptionalAn opaque string that is returned as a URI parameter in the authorization code flow, and in the URI fragment identifier in the implicit flow. The state can be useful for correlating requests and responses. Because your redirect_uri can be guessed, using a state value can increase your assurance that an incoming connection is the result of an authentication request initiated by your app. If you generate a random string or encode the hash of some client state (such as a cookie) in state, you can validate the response to additionally ensure that the request and response originate in the same browser. This provides protection against attacks such as cross-site request forgery.
ui_localesOptionalSpace-delimited list of preferred locales for the login UI, such as de-CH de en. If none is provided or matches the possible locales provided by the login UI, the accept-language header of the browser is taken into account.

Authorization code flow with PKCE

Authorization code flow with PKCE (Proof Key for Code Exchange) is an OAuth 2.0 authentication and authorization mechanism designed to improve the security of the authorization code flow.

The authorization server uses the same hash function to derive a code challenge from a code verifier and compares it to the code challenge it stored during the initial authorization request.

PKCE is a security extension to the authorization code flow that mitigates this attack by adding an additional code_verifier parameter to the initial authorization request. The client generates a code_verifier, which is a cryptographically random string of characters, and uses it to derive the code_challenge parameter using a one-way hash function. The code_challenge is sent along with the authorization request to the authorization server, which stores it.

The following table describes the new parameters in PKCE compared to the standard authorization code flow:

ParameterRequiredDescription
code_verifierRequiredA random string generated by the client app. It is used to create the code_challenge and is later sent to the server when exchanging the authorization code for an access token.
code_challengeRequiredCreates a SHA256 hash of the code_verifier and encodes is using Base64URL. The code_challenge is sent in the initial authentication request.
code_challenge_methodRequiredThe method used to create the code_challenge from the code_verifier. Typically, the method used is S256 (SHA-256 hashing), but it can also be plain (no hashing).

Generate a code verifier

Create a random string of length between 43 and 128 characters using characters from the sets [A-Z], [a-z], [0-9], -, ., \_, ~.

export function generateRandomString(length: number): string {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'
let result = ''
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length))
}
return result
}

Generate a code challenge

Create a code challenge from the received code_verifer.

function base64UrlEncode(buffer: Buffer) {
let base64 = buffer.toString('base64')
base64 = base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
return base64
}

export function generateCodeChallenge(codeVerifier: string) {
// Encode the code_verifier as bytes
const codeVerifierBytes = Buffer.from(codeVerifier, 'ascii')

// Hash the code_verifier using SHA-256
const codeChallengeBytes = crypto.createHash('sha256').update(codeVerifierBytes).digest()

// Encode the hashed code_verifier as Base64
const codeChallenge = base64UrlEncode(codeChallengeBytes)

return codeChallenge
}

To implement PKCE in your authorization process, when making the initial authentication request, include the code_challenge and code_challenge_method as query parameters. When exchanging the authorization code for an access token, include the code_verifier as a parameter in the request.

The authorization server then verifies the code_verifier and code_challenge pair before issuing an access token, ensuring that the client app making the request is the same one that initiated the authentication process.

Here's an example of an initial authentication request with PKCE:

GET /oauth2/auth?
Host: https://api-gateway.skymavis.com

client_id={client_id}
&redirect_uri={redirect_uri}
&response_type=code
&state=random_string
&scope=openid
&code_challenge={code_challenge}
&code_challenge_method=S256

And here's an example of an access token request with PKCE:

POST /account/oauth2/token
Host: https://api-gateway.skymavis.com
Content-Type: application/x-www-form-urlencoded
X-API-Key: {your_api_key}

grant_type=authorization_code
&code={authorization_code}
&redirect_uri={your_callback_url}
&client_id={your-client-id}
&client_secret={your-client-secret}
&code_verifier={code_verifier}

Implicit flow

The implicit flow is mainly used by clients implemented in a browser using a scripting language. The access token and ID token are returned directly to the client, which may expose them to the end user and apps that have access to the end user's user agent. The authorization server does not perform client authentication.

When using the implicit flow, the authorization endpoint https://api-gateway.skymavis.com/oauth2/auth is used in the same way as for the authorization code flow.

The difference between the implicit flow and authorization code flow lies in how the access token is obtained. In the implicit flow, the access token is returned directly from the authorization server to the client after the user has successfully authenticated, whereas in the authorization code flow, the authorization server returns an authorization code to the client, and then the client uses this authorization code to exchange for an access token.

Security warning

The implicit flow returns the access token directly and doesn't require security codes like the authorization code flow. If your app needs higher security, consider implementing the authorization code flow as a safer authentication method.

In the implicit flow, the authentication requests are constructed with the following parameters:

ParameterRequiredDescription
response_typeRequiredOAuth 2.0 response type value that determines the authorization processing flow to be used, including what parameters are returned from the endpoints used. When using the implicit flow, this value is id_token token or id_token.
redirect_uriRequiredAn authorized redirect URI for the given client_id specified in the Developer Console. When using the implicit flow, don't use the HTTP scheme unless the client is a native app, in which case it may use the HTTP scheme with localhost as the hostname.
nonceRequiredA random value generated by your app that enables replay protection when present.

Here's an example of an initial authentication request with the implicit flow:

GET /oauth2/auth?
Host: https://api-gateway.skymavis.com

client_id={client_id}
&redirect_uri={redirect_uri}
&response_type=id_token
&state=random_string
&scope=openid
&nonce={nonce}

Response parameters

If your response_type is either id_token or id_token token and no error occurs, the following response is returned:

ParameterDescription
access_tokenOnly returned if response_type included a token.
expires_inNumber of second until the expiration of the access token.
id_tokenAn ID token of the authorized user.
token_typeType of the access token. The value is always Bearer.
scopeScopes of the access token. These might differ from the provided scope parameter.
stateUnmodified state parameter from the request.

Resource owner password credentials flow

The resource owner password credentials (ROPC) flow allows an app to sign in the user with the Ronin Wallet extension or mobile Ronin Wallet app without opening a web browser. This flow is therefore also known as "Sign-In with Ronin Wallet." The flow describes how Ronin Wallet authenticates with Sky Mavis Account by signing a standard message format parameterized by scope, session details, and security mechanisms, such as a nonce.

Send a fetch nonce request

In the ROPC flow, the app first needs to send a fetch nonce request to the authorization server.

curl 'https://${DOMAIN}/account/oauth2/ronin/fetch-nonce?address={{ronin_address}}

The message must be generated following the ERC-4361: Sign-In with Ethereum standard with the format as follows:

${domain} wants you to sign in with your Ronin account:
${address}

${statement}

URI: ${uri}
Version: ${version}
Chain ID: ${chain-id}
Nonce: ${nonce}
Issued At: ${issued-at}
Expiration Time: ${expiration-time}
Not Before: ${not-before}
Request ID: ${request-id}
Resources:
- ${resources[0]}
- ${resources[1]}
...
- ${resources[n]}

Example nonce response:

{
"nonce": "17924493584421707055",
"issued_at": "2023-06-06T04:41:08Z",
"not_before": "2023-06-06T04:41:08Z",
"expiration_time": "2023-06-06T04:41:38Z"
}

Generate a message with the nonce

Generate a message with the nonce following EIP-4361.

Example message:

developers.skymavis.com wants you to sign in with your Ronin account:
0x6ca...8116

I accept the ServiceOrg Terms of Service: https://developers.skymavis.com/tos

URI: https://developers.skymavis.com/dashboard/login
Version: 1
Chain ID: 2020
Nonce: 17924493584421707055
Issued At: 2023-06-06T04:41:08Z
Expiration Time: 2023-06-06T04:41:38Z
Not Before: 2023-06-06T04:41:08Z

User signs the message

The user uses their Ronin Wallet to sign the message, after which the message is returned to the client app.

Authenticate with the message and signature

Send an authentication request containing the message and the signature:

curl --request POST \
--url 'https://{DOMAIN}/account/oauth2/token' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=ronin \
--data message={message} \
--data signature={signature} \
--data 'client_id={yourClientId}' \
--data 'client_secret={yourClientSecret}' \
--data 'scope=openid offline'

Token response:

{
"id_token": "eyJ...i",
"access_token": "eyJ...i",
"expires_in": 600,
"scope": "openid offline",
"token_type": "bearer"
}

Token exchange

Token exchange is a process in the authorization code flow where your app redeems the authorization code for access token and refresh token from the authorization server. For this to work, the app needs to authenticate with the authorization server through the client_secret_post token endpoint authentication method.

This authentication method sends the client ID and client secret in the body of the POST request to the authorization server. For example:

POST /account/oauth2/token
Host: https://api-gateway.skymavis.com
X-API-Key: {your_api_key}
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code={authorization_code}
&redirect_uri={redirect_uri}
&client_id={client_id}
&client_secret={client_secret}
PKCE flow

The code_verifier parameter is required in requests within the PKCE flow.

Refresh token

Refresh token is a token used to extend the life of the access token without the user having to re-authenticate. In the authorization code flow, after the app redeems the authorization code for the access token and refreshes the token, the app can use this refresh token to get a new access token when the old access token expires.

Similar to token exchange, when using the refresh token, the app needs to authenticate with the authorization server through the client_secret_post token endpoint authentication method. The following sections describe how to use the refresh token for theclient_secret_post method.

This authentication method sends the client ID and client secret in the body of the POST request to the authorization server. For example:

POST /account/oauth2/token
Host: https://api-gateway.skymavis.com
X-API-Key: {your_api_key}
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token={refresh_token}
&scope={scope}
&client_id={client_id}
&client_secret={client_secret}

Validate a token

You can use Sky Mavis Account to authenticate your end users and issue them signed access tokens and ID tokens, which your app can then use.

It is important that your app only uses the access token to grant access, and not the ID token. This is because access tokens are intended for authorizing access to a resource. ID tokens, on the other hand, are intended for authentication. They provide information about the resource owner to allow you to verify that they are who they say they are. Authentication is the concern of the clients. Because of this, when a client makes an authentication request, the ID token that is returned contains the client_id in the ID token's aud claim.

After the signed tokens are issued to the end users, they can be passed to your app for validation. There are two ways to verify a token: locally or remotely with Sky Mavis. The token is signed with a JSON Web Key (JWK) using the RS256 algorithm. To validate the signature, Sky Mavis provides your app with a public key that can be used.

ID token

The following is an example of an ID token:

{
"acr": "google",
"amr": [
"oidc"
],
"at_hash": "7Y1j0a-pn_RQXEW4vsLkHw",
"aud": [
"{client_id}"
],
"auth_time": 1683283201,
"email": "test@skymavis.com",
"exp": 1683888020,
"iat": 1683283220,
"iss": "https://athena.skymavis.com/",
"jti": "06934da0-03cc-483b-9d78-eb7ff143aec9",
"name": "test user",
"nonce": "1683283184931",
"rat": 1683283185,
"redirect_uri": "http//localhost/callback",
"roninAddress": "0x000000",
"sid": "5c4feba2-02ec-49cd-9d6c-257b29c57e2f",
"sub": "{mavis_id}",
"walletConnect": null
}

Access token

The following is an example of an access token:

{
"aud": [],
"client_id": "{client_id}",
"email": "test@skymavis.com",
"exp": 1683888020,
"ext": {
"addr": "0x378be4d345d68fd5f37d2de999e4c45890978b8d",
"email": "test@skymavis.com",
"name": "test user",
"redirect_uri": "http//localhost/callback",
"roninAddress": "0x000000",
"walletConnect": null
},
"iat": 1683283219,
"iss": "https://athena.skymavis.com/",
"jti": "8b8caa11-e6b4-4a7f-b37a-1ca034686acf",
"name": "test user",
"nbf": 1683283219,
"roninAddress": "0x000000",
"scp": [
"openid",
"offline"
],
"sub": "1ed7b7f2-5404-617b-95ec-541d04a831fd"
}

Discovery document

Authenticating users and requesting resources, such as tokens, users information, and public keys, require multiple endpoints under OIDC.

OIDC uses an entity called a "Discovery document" to simplify implementations and increase flexibility. This Discovery document is a JSON document that contains key-value pairs providing information about the OIDC provider's configuration, such as the URIs of the authorization, token, revocation, userinfo, and public-key endpoints.

You can retrieve the discovery document for Sky Mavis's OIDC services from the following URL: https://api-gateway.skymavis.com/account/.well-known/openid-configuration.

To use Sky Mavis's OIDC services, you need to hard-code the discovery document URI into your app. Your app fetches the document, applies caching rules in the response, then retrieves endpoint URIs from it as needed.

Was this helpful?
Happy React is loading...