12 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	title
| title | 
|---|
| Discord | 
Support level: authentik
Allows users to authenticate using their Discord credentials
Preparation
The following placeholders will be used:
- authentik.companyis the FQDN of the authentik install.
Discord
- Create an application in the Discord Developer Portal (This is Free) https://discord.com/developers/applications
- Name the Application
- 
Select OAuth2 from the left Menu 
- 
Copy the Client ID and save it for later 
- 
Click to Reveal the Client Secret and save it for later 
- 
Click Add Redirect and add https://authentik.company/source/oauth/callback/discord/ 
Here is an example of a completed OAuth2 screen for Discord.
authentik
- 
Under Directory -> Federation & Social login Click Create Discord OAuth Source 
- 
Name: Choose a name (For the example I used Discord) 
- 
Slug: discord (You can choose a different slug, if you do you will need to update the Discord redirect URLand point it to the correct slug.) 
- 
Consumer Key: Client ID from step 4 
- 
Consumer Secret: Client Secret from step 5 
Here is an example of a complete authentik Discord OAuth Source
Save, and you now have Discord as a source.
:::note For more details on how-to have the new source display on the Login Page see here. :::
Checking for membership of a Discord Guild
:::info
Ensure that the Discord OAuth source in 'Federation & Social login' has the additional guilds scope added under the 'Protocol settings'.
:::
Create a new 'Expression Policy' with the content below, adjusting the variables where required:
# To get the guild ID number for the parameters, open Discord, go to Settings > Advanced and enable developer mode.
# Right-click on the server/guild title and select "Copy ID" to get the guild ID.
ACCEPTED_GUILD_ID = "123456789123456789"
GUILD_NAME_STRING = "The desired server/guild name in the error message."
# Only change below here if you know what you are doing.
# Ensure flow is only run during OAuth logins via Discord
if context['source'].provider_type != "discord":
    return True
# Get the user-source connection object from the context, and get the access token
connection = context.get("goauthentik.io/sources/connection")
if not connection:
  return False
access_token = connection.access_token
guilds = requests.get(
    "https://discord.com/api/users/@me/guilds",
    headers= {
        "Authorization": f"Bearer {access_token}",
    }
).json()
user_matched = any(ACCEPTED_GUILD_ID == g["id"] for g in guilds)
if not user_matched:
    ak_message(f"User is not a member of {GUILD_NAME_STRING}.")
return user_matched
Now bind this policy to the chosen enrollment and authentication flows for the Discord OAuth source.
Checking for membership of a Discord Guild role
:::info
Ensure that the Discord OAuth source in 'Federation & Social login' has the additional guilds guilds.members.read scopes added under the 'Protocol settings'.
:::
Create a new 'Expression Policy' with the content below, adjusting the variables where required:
# To get the role and guild ID numbers for the parameters, open Discord, go to Settings > Advanced and
# enable developer mode.
# Right-click on the server/guild title and select "Copy ID" to get the guild ID.
# Right-click on the server/guild title and select server settings > roles, right click on the role and click
# "Copy ID" to get the role ID.
ACCEPTED_ROLE_ID = "123456789123456789"
ACCEPTED_GUILD_ID = "123456789123456789"
GUILD_NAME_STRING = "The desired server/guild name in the error message."
ROLE_NAME_STRING = "The desired role name in the error message."
# Only change below here if you know what you are doing.
GUILD_API_URL = f"https://discord.com/api/users/@me/guilds/{ACCEPTED_GUILD_ID}/member"
# Ensure flow is only run during OAuth logins via Discord
if context['source'].provider_type != "discord":
    return True
# Get the user-source connection object from the context, and get the access token
connection = context.get("goauthentik.io/sources/connection")
if not connection:
  return False
access_token = connection.access_token
guild_member_object = requests.get(
    GUILD_API_URL,
    headers= {
        "Authorization": f"Bearer {access_token}",
    }
).json()
# The response for JSON errors is held within guild_member_object['code']
# See: https://discord.com/developers/docs/topics/opcodes-and-status-codes#json
# If the user isn't in the queried guild, it gives the somewhat misleading code = 10004.
if "code" in guild_member_object:
    if guild_member_object['code'] == 10004:
        ak_message(f"User is not a member of {GUILD_NAME_STRING}.")
    else:
        ak_create_event("discord_error", source=context['source'], code=guild_member_object['code'])
        ak_message("Discord API error, try again later.")
    # Policy does not match if there is any error.
    return False
user_matched = any(ACCEPTED_ROLE_ID == g for g in guild_member_object["roles"])
if not user_matched:
    ak_message(f"User is not a member of the {ROLE_NAME_STRING} role in {GUILD_NAME_STRING}.")
return user_matched
Now bind this policy to the chosen enrollment and authentication flows for the Discord OAuth source.
Syncing Discord roles to authentik groups
:::info
Ensure that the Discord OAuth source in 'Federation & Social login' has the additional guilds.members.read scopes added under the 'Protocol settings'.
:::
Create a new 'Expression Policy' with the content below, adjusting the variables where required.
Sync on enrollment
# To get the role and guild ID numbers for the parameters, open Discord, go to Settings > Advanced and
# enable developer mode.
# Right-click on the server/guild title and select "Copy ID" to get the guild ID.
# Right-click on the server/guild title and select server settings > roles, right click on the role and click
# "Copy ID" to get the role ID.
from authentik.core.models import Group
GUILD_ID = "<YOUR GUILD ID>"
MAPPED_ROLES = {
    "<Discord Role Id 1>": Group.objects.get_or_create(name="<Authentik Role Name 1>")[0],
    "<Discord Role Id 2>": Group.objects.get_or_create(name="<Authentik Role Name 2>")[0],
    # You can add mapped roles by copying the above line and adjusting to your needs
}
# Only change below here if you know what you are doing.
GUILD_API_URL = "https://discord.com/api/users/@me/guilds/{guild_id}/member"
# Ensure flow is only run during OAuth logins via Discord
if context["source"].provider_type != "discord":
    return True
# Get the user-source connection object from the context, and get the access token
connection = context.get("goauthentik.io/sources/connection")
if not connection:
    return False
access_token = connection.access_token
guild_member_info = requests.get(
    GUILD_API_URL.format(guild_id=GUILD_ID),
    headers={"Authorization": "Bearer " + access_token},
).json()
# Ensure user is a member of the guild
if "code" in guild_member_info:
    if guild_member_info["code"] == 10004:
        ak_message("User is not a member of the guild")
    else:
        ak_create_event(
            "discord_error", source=context["source"], code=guild_member_info["code"]
        )
        ak_message("Discord API error, try again later.")
    return False
# Add all mapped roles the user has in the guild
groups_to_add = []
for role_id in MAPPED_ROLES:
    if role_id in guild_member_info["roles"]:
        groups_to_add.append(MAPPED_ROLES[role_id])
request.context["flow_plan"].context["groups"] = groups_to_add
return True
Now bind this policy to the chosen enrollment flows for the Discord OAuth source.
Sync on authentication
# To get the role and guild ID numbers for the parameters, open Discord, go to Settings > Advanced and
# enable developer mode.
# Right-click on the server/guild title and select "Copy ID" to get the guild ID.
# Right-click on the server/guild title and select server settings > roles, right click on the role and click
# "Copy ID" to get the role ID.
from authentik.core.models import Group
GUILD_ID = "<YOUR GUILD ID>"
MAPPED_ROLES = {
    "<Discord Role Id 1>": Group.objects.get_or_create(name="<Authentik Role Name 1>")[0],
    "<Discord Role Id 2>": Group.objects.get_or_create(name="<Authentik Role Name 2>")[0],
    # You can add mapped roles by copying the above line and adjusting to your needs
}
# Only change below here if you know what you are doing.
GUILD_API_URL = "https://discord.com/api/users/@me/guilds/{guild_id}/member"
# Ensure flow is only run during OAuth logins via Discord
if context["source"].provider_type != "discord":
    return True
# Get the user-source connection object from the context, and get the access token
connection = context.get("goauthentik.io/sources/connection")
if not connection:
    return False
access_token = connection.access_token
guild_member_info = requests.get(
    GUILD_API_URL.format(guild_id=GUILD_ID),
    headers={"Authorization": "Bearer " + access_token},
).json()
# Ensure user is a member of the guild
if "code" in guild_member_info:
    if guild_member_info["code"] == 10004:
        ak_message("User is not a member of the guild")
    else:
        ak_create_event(
            "discord_error", source=context["source"], code=guild_member_info["code"]
        )
        ak_message("Discord API error, try again later.")
    return False
# Get the user's current roles and remove all roles we want to remap
new_groups = [
    role for role in request.user.ak_groups.all() if role not in MAPPED_ROLES.values()
]
# Add back mapped roles which the user has in the guild
for role_id in MAPPED_ROLES:
    if role_id in guild_member_info["roles"]:
        new_groups.append(MAPPED_ROLES[role_id])
# Update user's groups
request.user.ak_groups.set(new_groups)
request.user.save()
return True
Now bind this policy to the chosen authentication flows for the Discord OAuth source.
Store OAuth info in attribute and create avatar attribute from Discord avatar
:::info
Ensure that the Discord OAuth source in 'Federation & Social login' has the additional guilds.members.read scopes added under the 'Protocol settings'.
:::
:::info In order to use the created attribute in authentik you will have to set authentik configuration arguments found at: https://docs.goauthentik.io/docs/core/settings#avatars :::
Create a new 'Expression Policy' with the content below, adjusting the variables where required:
import base64
import requests
AVATAR_SIZE = "64"  # Valid values: 16,32,64,128,256,512,1024
# Only change below here if you know what you are doing.
AVATAR_URL = "https://cdn.discordapp.com/avatars/{id}/{avatar}.png?site={avatar_size}"
AVATAR_STREAM_CONTENT = "data:image/png;base64,{base64_string}"  # Converts base64 image into html syntax usable with authentik's avatar attributes feature
def get_as_base64(url):
    """Returns the base64 content of the url"""
    return base64.b64encode(requests.get(url).content)
def get_avatar_from_avatar_url(url):
    """Returns an authentik-avatar-attributes-compatible string from an image url"""
    cut_url = f"{url}?size=64"
    return AVATAR_STREAM_CONTENT.format(
        base64_string=(get_as_base64(cut_url).decode("utf-8"))
    )
# Ensure flow is only run during OAuth logins via Discord
if context["source"].provider_type != "discord":
    return True
user = request.user
userinfo = request.context["oauth_userinfo"]
# Assigns the discord attributes to the user
user.attributes["discord"] = {
    "id": userinfo["id"],
    "username": userinfo["username"],
    "discriminator": userinfo["discriminator"],
    "email": userinfo["email"],
    "avatar": userinfo["avatar"],
    "avatar_url": (
        AVATAR_URL.format(
            id=userinfo["id"], avatar=userinfo["avatar"], avatar_size=AVATAR_SIZE
        )
        if userinfo["avatar"]
        else None
    ),
}
# If the user has an avatar, assign it to the user
avatar_url = user.attributes["discord"].get("avatar_url", None)
if avatar_url is not None:
    user.attributes["avatar"] = get_avatar_from_avatar_url(avatar_url)
user.save()
return True
Now bind this policy to the chosen enrollment and authentication flows for the Discord OAuth source.
 
			


