The Discord OAuth endpoints handle user authentication through Discord, guild membership verification, and session creation with JWT tokens.
OAuth Initiation
Endpoint
Initiates the Discord OAuth flow by redirecting to Discord’s authorization page.
Implementation
#[get( "/auth/discord" )]
pub fn discord_auth () -> Redirect {
let client_id = std :: env :: var ( "DISCORD_CLIENT_ID" )
. expect ( "DISCORD_CLIENT_ID must be set" );
let redirect_uri = std :: env :: var ( "DISCORD_REDIRECT_URI" )
. expect ( "DISCORD_REDIRECT_URI must be set" );
let discord_auth_url = format! (
"https://discord.com/api/oauth2/authorize?client_id={}&redirect_uri={}&response_type=code&scope=identify+guilds" ,
client_id , redirect_uri
);
Redirect :: to ( discord_auth_url )
}
OAuth Scopes
Scope Purpose identifyAccess user ID, username, and avatar guildsCheck server membership
Usage
Simply redirect users to this endpoint:
< a href = "/api/auth/discord" > Login with Discord </ a >
OAuth Callback
Endpoint
GET /api/auth/discord/callback?code={authorization_code}
Handles the Discord OAuth callback, exchanges the authorization code for tokens, verifies guild membership, and creates a session.
Implementation
#[get( "/auth/discord/callback?<code>" )]
pub async fn discord_callback (
code : String ,
jar : & CookieJar <' _ >
) -> Result < Template , rocket :: http :: Status > {
let client = reqwest :: Client :: new ();
// Get OAuth tokens
let token_response = get_discord_token ( & client , & code ) . await
. map_err ( | _ | rocket :: http :: Status :: InternalServerError ) ? ;
// Get user info
let user_info = get_discord_user ( & client , & token_response . access_token) . await
. map_err ( | _ | rocket :: http :: Status :: InternalServerError ) ? ;
let guilds = get_discord_guilds ( & client , & token_response . access_token) . await
. map_err ( | _ | rocket :: http :: Status :: InternalServerError ) ? ;
// Check guild membership
let guild_id = std :: env :: var ( "DISCORD_SERVER_ID" )
. expect ( "DISCORD_SERVER_ID must be set" )
. parse :: < u64 >()
. expect ( "DISCORD_SERVER_ID must be a number" );
let is_member = guilds . iter ()
. any ( | guild | guild . id . parse :: < u64 >() . unwrap () == guild_id );
if ! is_member {
return Ok ( Template :: render ( "noAccess" , & {
let mut context = std :: collections :: HashMap :: new ();
context . insert ( "serverName" , "HS Kempten" . to_string ());
context
}));
}
// Create user token
let user = User :: new ( user_info . id . parse () . unwrap ());
let token = if user_info . id == "242385294123335690" {
user . create_token ( Roles :: Admin )
} else {
user . create_token ( Roles :: User )
};
jar . add ( Cookie :: build (( "token" , token ))
. path ( "/" )
. secure ( true )
. http_only ( true ));
// Render success template
Ok ( Template :: render ( "discord_callback" , & {
let mut context = std :: collections :: HashMap :: new ();
context . insert ( "user_id" , user_info . id . clone ());
context . insert ( "username" , user_info . username . clone ());
context . insert ( "avatar" , user_info . get_avatar_url ());
context
}))
}
Query Parameters
Discord authorization code provided by Discord after user authorization
Response
On success, renders discord_callback.html.hbs template with user information and sets authentication cookie.
On failure (non-member), renders noAccess.html.hbs template.
OAuth Flow Diagram
Data Structures
TokenResponse
#[derive( Deserialize )]
pub struct TokenResponse {
access_token : String ,
}
Returned by Discord’s token exchange endpoint.
UserInfo
#[derive( Deserialize )]
pub struct UserInfo {
id : String ,
username : String ,
avatar : String ,
}
impl UserInfo {
fn get_avatar_url ( & self ) -> String {
let ext = if self . avatar . starts_with ( "a_" ) { "gif" } else { "png" };
format! (
"https://cdn.discordapp.com/avatars/{}/{}.{}" ,
self . id, self . avatar, ext
)
}
}
Animated avatars (starting with “a_”) use .gif extension, static avatars use .png.
Guild
#[derive( Deserialize , Debug )]
#[serde(rename_all = "camelCase" )]
pub struct Guild {
id : String ,
name : String ,
icon : Option < String >,
owner : bool ,
permissions : u64 ,
}
Helper Functions
Exchange Authorization Code
async fn get_discord_token (
client : & reqwest :: Client ,
code : & str
) -> Result < TokenResponse , reqwest :: Error > {
client . post ( "https://discord.com/api/oauth2/token" )
. form ( & [
( "client_id" , std :: env :: var ( "DISCORD_CLIENT_ID" ) . expect ( "DISCORD_CLIENT_ID must be set" )),
( "client_secret" , std :: env :: var ( "DISCORD_CLIENT_SECRET" ) . expect ( "DISCORD_CLIENT_SECRET must be set" )),
( "grant_type" , "authorization_code" . to_string ()),
( "code" , code . to_string ()),
( "redirect_uri" , std :: env :: var ( "DISCORD_REDIRECT_URI" ) . expect ( "DISCORD_REDIRECT_URI must be set" )),
( "scope" , "identify" . to_string ()),
])
. send ()
. await ?
. json ()
. await
}
async fn get_discord_user (
client : & reqwest :: Client ,
access_token : & str
) -> Result < UserInfo , reqwest :: Error > {
client . get ( "https://discord.com/api/users/@me" )
. header ( "Authorization" , format! ( "Bearer {}" , access_token ))
. send ()
. await ?
. json ()
. await
}
Get User Guilds
pub async fn get_discord_guilds (
client : & reqwest :: Client ,
access_token : & str
) -> Result < Vec < Guild >, reqwest :: Error > {
client . get ( "https://discord.com/api/users/@me/guilds" )
. header ( "Authorization" , format! ( "Bearer {}" , access_token ))
. send ()
. await ?
. json :: < Vec < Guild >>()
. await
}
OAuth Client (Alternative Implementation)
A reusable OAuth client is also available:
#[derive( Clone )]
pub struct DiscordOAuthClient {
client : Client ,
client_id : String ,
client_secret : String ,
redirect_uri : String ,
api_base : String ,
}
impl DiscordOAuthClient {
pub fn new () -> Result < Self , std :: env :: VarError > {
Ok ( Self {
client : Client :: new (),
client_id : std :: env :: var ( "DISCORD_CLIENT_ID" ) ? ,
client_secret : std :: env :: var ( "DISCORD_CLIENT_SECRET" ) ? ,
redirect_uri : std :: env :: var ( "DISCORD_REDIRECT_URI" ) ? ,
api_base : "https://discord.com/api" . to_string (),
})
}
pub async fn exchange_code ( & self , code : & str ) -> Result < TokenResponse , reqwest :: Error > {
let form = [
( "client_id" , & self . client_id),
( "client_secret" , & self . client_secret),
( "grant_type" , & "authorization_code" . to_string ()),
( "code" , & code . to_string ()),
( "redirect_uri" , & self . redirect_uri),
( "scope" , & "identify" . to_string ()),
];
self . client . post ( format! ( "{}/oauth2/token" , self . api_base))
. form ( & form )
. send ()
. await ?
. json ()
. await
}
pub async fn get_current_user ( & self , access_token : & str ) -> Result < UserInfo , reqwest :: Error > {
self . get_authenticated_resource ( "users/@me" , access_token ) . await
}
pub async fn get_user_guilds ( & self , access_token : & str ) -> Result < Vec < Guild >, reqwest :: Error > {
self . get_authenticated_resource ( "users/@me/guilds" , access_token ) . await
}
}
Environment Configuration
Required environment variables:
Your Discord application’s client ID
Your Discord application’s client secret (keep private!)
OAuth callback URL (must match Discord app settings) Example: https://bot.example.com/api/auth/discord/callback
Discord server ID that users must be members of
Security Considerations
Critical Security Requirements:
HTTPS Only : OAuth should only run over HTTPS in production
Secret Protection : Never expose DISCORD_CLIENT_SECRET
Cookie Security : Cookies are marked secure and http_only
Guild Verification : Always verify guild membership
Token Validation : JWT tokens expire after 24 hours
Cookie Configuration
jar . add ( Cookie :: build (( "token" , token ))
. path ( "/" ) // Available site-wide
. secure ( true ) // HTTPS only
. http_only ( true )); // No JavaScript access
Error Handling
Error Scenario Response Template Invalid authorization code 500 Internal Server Error None Discord API failure 500 Internal Server Error None Not guild member 200 OK noAccess.html.hbsSuccess 200 OK discord_callback.html.hbs
Testing
Test the OAuth flow locally:
Set environment variables in .env
Configure Discord app with redirect URI: http://localhost:8000/api/auth/discord/callback
Visit http://localhost:8000/api/auth/discord
Authorize the application
Check for JWT token in browser cookies
For local testing, Discord allows http://localhost redirect URIs. Production must use HTTPS.
Authentication System Learn about JWT tokens and roles
User Management Managing authenticated users
Admin Dashboard Admin role assignment and access