Table Of Contents
Introduction
I once needed to connect users Discord accounts with their Steam accounts. So I just asked them to enter their SteamId and then connected them using a simple database table. I thought that was it, but then someone brought the fact to me, that people could enter someone else's SteamId to impersonate them and take actions in their account. So I tried finding a way to fix it and that lead me to my biggest coding nightmare, working with Discord OAuth.
What is even OAuth
Cloudflare defines OAuth as follows: "OAuth is a technical standard for authorizing users. It is a protocol for passing authorization from one service to another without sharing the actual user credentials, such as a username and password" [Source]
We can compile this down to the following statement, "OAuth allows us to access certain information without credential sharing". For Discord this allows us to get account connections, guilds (Discord servers) or even E-Mails without the user having to provide their password. It should be self explanatory why this is useful but to boil it down, it allows us to interact with certain user information in a safe and private manner.
Using OAuth
Let's look into OAuth from the problem described in the introduction. We need to connect a Discord UserId with a SteamId, so we need two different OAuth scopes. These are identify
, which allows us to use the user/@me
endpoint, for querying user profile information and the connections
scope, which gives us access to the /users/@me/connections
endpoint, which returns connections information.
So what steps do we need now, that we know what information we need to query?
- Creating the OAuth link
- Creating a simple WebServer for redirects
- Making the Api requests
Creating the OAuth link
The OAuth link, is generated by Discord and given to the user, for them to authorize the data transfer. It is really simple to create one, although you have to take some things into account.
Firstly, navigate to the Discord Developer Portal, then click on your application and find the OAuth section. Here we need to add the redirect, that Discord will send the user to. In our case we will simply use a localhost:
Next, we'll select our scopes (identify
and connections
) in the URL generator:
And at last, just select the redirect you entered previously and your URL will be shown below:
When you provide a user with this link, they will be prompted with this popup. After clicking on authorize they will then be redirected to your page, allowing us to start using OAuth.
Creating a simple WebServer
Oauth requests from Discord work in the following way:
- User authorizes through your OAuth link
- Discord redirects them to your WebServer with an authorization code attached
- You send a request to discord to get an authorization token
- You query data using the token
It sounds really simple and it is really simple, but it took way to long for me to understand this, so let's go through it step by step. We firstly create a simple WebServer for our OAuth redirect:
embeddedServer(Netty, 8080) {
routing {
get("discord/redirect/oauth") {
}
}
}.start(true)
After that, we just have to search for the authorization code in the request parameters and maybe add a small response text (this code is in the get... section):
call.respondText("The redirect was successful, you can close this page now")
val authorizationCode = call.request.queryParameters["code"]
Making the api requests
After receiving our authorization code, we still need to get the authorization token, to actually start querying user data. We'll start by creating a json decoder plus an Http-Client for our request:
val json = Json {
ignoreUnknownKeys = true
}
val client = HttpClient(CIO) {
install(ClientContentNegotiation) {
(json)
formData()
}
}
Now we need a data class that our json decoder can use for the serialization process (for other languages, use the best json decoding manner available):
@Serializable
data class DiscordTokenResponse(
@SerialName("access_token")
val accessToken: String,
@SerialName("token_type")
val tokenType: String,
@SerialName("expires_in")
val expiresIn: Long,
@SerialName("refresh_token")
val refreshToken: String,
@SerialName("scope")
val scope: String
)
Then we'll create our request and send it using our created client:
val tokenCall = client.post("https://discord.com/api/oauth2/token") {
setBody(FormDataContent(Parameters.build {
append("client_id", clientId)
append("client_secret", clientSecret)
append("grant_type", "authorization_code")
append("code", authorizationCode)
append("redirect_uri", uri)
}))
}
At last we'll check for the request's success and then serialize our json output:
if (!tokenCall.status.isSuccess()) return
val tokenResponse = json.decodeFromString<DiscordTokenResponse>(tokenCall.bodyAsText())
Now we have our lovely token. We can now use this token to firstly get our userId and a corresponding data class:
@Serializable
data class DiscordUser(
val id: String,
val username: String,
)
val userCall = client.get("https://discord.com/api/users/@me") {
header("Authorization", "Bearer ${tokenResponse.accessToken}")
}
if (!userCall.status.isSuccess()) return
val discordUser = json.decodeFromString<DiscordUser>(userCall.bodyAsText())
Then we follow up with our connection request and the corresponding data class:
@Serializable
data class DiscordConnection(
val id: String,
val name: String,
val type: String,
@SerialName("friend_sync")
val friendSync: Boolean,
@SerialName("metadata_visibility")
val metadataVisibility: Int,
@SerialName("show_activity")
val showActivity: Boolean,
@SerialName("two_way_link")
val twoWayLink: Boolean,
val verified: Boolean,
val visibility: Int
)
val connectionCall = client.get("https://discord.com/api/users/@me/connections") {
header("Authorization", "Bearer ${tokenResponse.accessToken}")
}
if (!connectionCall.status.isSuccess()) return
val connections = json.decodeFromString<List<DiscordConnection>>(connectionCall.bodyAsText())
Now that we have our values, we can simply connect them, completing our little OAuth adventure.
Conclusion
Discord OAuth, or OAuth in general does take a bit to get used to, but isn't as complicated as I once thought. I had a lot of problems with it, when I first tried to use it and needed help by other developers to do so. But after a lot of time and stress I finally understood it. The thing I learned from this, you just have to try over and over until you succeed.
This was more of a tutorial article, hope that it was still somewhat entertaining and informational. The examples are in Kotlin, but the general structure can be applied to all languages.
Sources
- The cover image was taken from discord's official branding page
- Definition of OAuth was taken from this Cloudflair Article
- Information from this German Wikipedia Article was also used for research
- The Discord Developer Docs were used for the endpoint and OAuth information
Top comments (0)