diff --git a/src/main/java/pw/kaboom/extras/commands/CommandSkin.java b/src/main/java/pw/kaboom/extras/commands/CommandSkin.java
index 8209541..af61ada 100644
--- a/src/main/java/pw/kaboom/extras/commands/CommandSkin.java
+++ b/src/main/java/pw/kaboom/extras/commands/CommandSkin.java
@@ -1,5 +1,7 @@
 package pw.kaboom.extras.commands;
 
+import java.util.HashMap;
+import java.util.Map;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
 import org.bukkit.command.Command;
@@ -7,12 +9,12 @@ import org.bukkit.command.CommandExecutor;
 import org.bukkit.command.CommandSender;
 import org.bukkit.command.ConsoleCommandSender;
 import org.bukkit.entity.Player;
-import pw.kaboom.extras.helpers.SkinDownloader;
+import pw.kaboom.extras.skin.SkinManager;
 
 import javax.annotation.Nonnull;
 
 public final class CommandSkin implements CommandExecutor {
-    private long millis;
+    private final Map<Player, Long> lastUsedMillis = new HashMap<>();
 
     @Override
     public boolean onCommand(final @Nonnull CommandSender sender,
@@ -26,11 +28,12 @@ public final class CommandSkin implements CommandExecutor {
         }
 
         final Player player = (Player) sender;
+        final long millis = lastUsedMillis.getOrDefault(player, 0L);
         final long millisDifference = System.currentTimeMillis() - millis;
 
         if (args.length == 0) {
             player.sendMessage(Component
-                    .text("Usage: /" + label + " <username>",
+                    .text("Usage: /" + label + " <username>\n/" + label + "off",
                             NamedTextColor.RED));
             return true;
         }
@@ -41,13 +44,25 @@ public final class CommandSkin implements CommandExecutor {
             return true;
         }
 
+        lastUsedMillis.put(player, System.currentTimeMillis());
+
         final String name = args[0];
+
+        if (name.equalsIgnoreCase("off") || name.equalsIgnoreCase("remove")
+         || name.equalsIgnoreCase("disable")) {
+            SkinManager.resetSkin(player, true);
+            return true;
+        }
+
+        if (name.equalsIgnoreCase("auto") || name.equalsIgnoreCase("default")
+        || name.equalsIgnoreCase("reset")) {
+            SkinManager.applySkin(player, player.getName(), true);
+            return true;
+        }
+
         final boolean shouldSendMessage = true;
 
-        SkinDownloader skinDownloader = new SkinDownloader();
-        skinDownloader.applySkin(player, name, shouldSendMessage);
-
-        millis = System.currentTimeMillis();
+        SkinManager.applySkin(player, name, shouldSendMessage);
         return true;
     }
 }
diff --git a/src/main/java/pw/kaboom/extras/commands/CommandUsername.java b/src/main/java/pw/kaboom/extras/commands/CommandUsername.java
index 7eaa5b0..2befeb7 100644
--- a/src/main/java/pw/kaboom/extras/commands/CommandUsername.java
+++ b/src/main/java/pw/kaboom/extras/commands/CommandUsername.java
@@ -1,6 +1,8 @@
 package pw.kaboom.extras.commands;
 
 import com.destroystokyo.paper.profile.PlayerProfile;
+import java.util.HashMap;
+import java.util.Map;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
 import org.bukkit.Bukkit;
@@ -14,7 +16,7 @@ import org.bukkit.entity.Player;
 import javax.annotation.Nonnull;
 
 public final class CommandUsername implements CommandExecutor {
-    private long millis;
+    private final Map<Player, Long> lastUsedMillis = new HashMap<>();
 
     @Override
     public boolean onCommand(final @Nonnull CommandSender sender,
@@ -31,6 +33,7 @@ public final class CommandUsername implements CommandExecutor {
         final String nameColor = ChatColor.translateAlternateColorCodes(
                 '&', String.join(" ", args));
         final String name = nameColor.substring(0, Math.min(16, nameColor.length()));
+        final long millis = lastUsedMillis.getOrDefault(player, 0L);
         final long millisDifference = System.currentTimeMillis() - millis;
 
         if (args.length == 0) {
@@ -62,7 +65,7 @@ public final class CommandUsername implements CommandExecutor {
 
         profile.setName(name);  // FIXME: Marked for removal
         player.setPlayerProfile(profile);
-        millis = System.currentTimeMillis();
+        lastUsedMillis.put(player, System.currentTimeMillis());
 
         player.sendMessage(
             Component.text("Successfully set your username to \"")
diff --git a/src/main/java/pw/kaboom/extras/helpers/SkinDownloader.java b/src/main/java/pw/kaboom/extras/helpers/SkinDownloader.java
deleted file mode 100644
index 9dc4ea3..0000000
--- a/src/main/java/pw/kaboom/extras/helpers/SkinDownloader.java
+++ /dev/null
@@ -1,115 +0,0 @@
-package pw.kaboom.extras.helpers;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.UUID;
-
-import javax.net.ssl.HttpsURLConnection;
-
-import org.bukkit.entity.Player;
-import org.bukkit.plugin.java.JavaPlugin;
-import org.bukkit.scheduler.BukkitRunnable;
-
-import com.destroystokyo.paper.profile.PlayerProfile;
-import com.destroystokyo.paper.profile.ProfileProperty;
-
-import net.kyori.adventure.text.Component;
-
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-
-import pw.kaboom.extras.Main;
-
-public final class SkinDownloader {
-    private static HashMap<UUID, PlayerProfile> skinProfiles = new HashMap<UUID, PlayerProfile>();
-
-    private HttpsURLConnection skinConnection;
-    private InputStreamReader skinStream;
-
-    private String texture;
-    private String signature;
-
-    public void applySkin(final Player player, final String name, final boolean shouldSendMessage) {
-        new BukkitRunnable() {
-            @Override
-            public void run() {
-                final PlayerProfile profile = player.getPlayerProfile();
-
-                try {
-                    fetchSkinData(name);
-                    profile.setProperty(new ProfileProperty("textures", texture, signature));
-
-                    if (shouldSendMessage) {
-                        player.sendMessage(
-                            Component.text("Successfully set your skin to ")
-                                .append(Component.text(name))
-                                .append(Component.text("'s"))
-                        );
-                    }
-                } catch (Exception exception) {
-                    try {
-                        skinStream.close();
-                        skinConnection.disconnect();
-                    } catch (Exception ignored) {
-                    }
-
-                    if (shouldSendMessage) {
-                        player.sendMessage(Component
-                            .text("A player with that username doesn't exist"));
-                    }
-
-                    return;
-                }
-
-                new BukkitRunnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            player.setPlayerProfile(profile);
-                        } catch (Exception ignored) {
-                        }
-                    }
-                }.runTask(JavaPlugin.getPlugin(Main.class));
-            }
-        }.runTaskAsynchronously(JavaPlugin.getPlugin(Main.class));
-    }
-
-    public void fillJoinProfile(final PlayerProfile profile, final String name, final UUID uuid) {
-        try {
-            fetchSkinData(name);
-            profile.setProperty(new ProfileProperty("textures", texture, signature));
-            skinProfiles.put(uuid, profile);
-        } catch (Exception exception) {
-            try {
-                skinStream.close();
-                skinConnection.disconnect();
-            } catch (Exception ignored) {
-            }
-        }
-    }
-
-    private void fetchSkinData(final String playerName) throws IOException {
-        final URL skinUrl = new URL("https://api.ashcon.app/mojang/v2/user/" + playerName);
-        skinConnection = (HttpsURLConnection) skinUrl.openConnection();
-        skinConnection.setConnectTimeout(0);
-
-        skinStream = new InputStreamReader(skinConnection.getInputStream());
-        final JsonObject responseJson = new JsonParser().parse(skinStream).getAsJsonObject();
-        final JsonObject rawSkin = responseJson.getAsJsonObject("textures").getAsJsonObject("raw");
-        texture = rawSkin.get("value").getAsString();
-        signature = rawSkin.get("signature").getAsString();
-
-        skinStream.close();
-        skinConnection.disconnect();
-    }
-
-    public static PlayerProfile getProfile(final UUID uuid) {
-        return skinProfiles.get(uuid);
-    }
-
-    public static void removeProfile(final UUID uuid) {
-        skinProfiles.remove(uuid);
-    }
-}
diff --git a/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java b/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java
index 4472a70..a15df64 100644
--- a/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java
+++ b/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java
@@ -6,10 +6,12 @@ import java.util.concurrent.ThreadLocalRandom;
 
 import org.bukkit.Bukkit;
 import org.bukkit.Location;
+import org.bukkit.Server;
 import org.bukkit.World;
 import org.bukkit.configuration.file.FileConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
 import org.bukkit.event.Listener;
 import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
 import org.bukkit.event.player.PlayerJoinEvent;
@@ -28,6 +30,7 @@ import com.google.common.base.Charsets;
 
 import pw.kaboom.extras.Main;
 import pw.kaboom.extras.modules.server.ServerTabComplete;
+import pw.kaboom.extras.skin.SkinManager;
 
 public final class PlayerConnection implements Listener {
     private static final FileConfiguration CONFIG = JavaPlugin.getPlugin(Main.class).getConfig();
@@ -115,11 +118,12 @@ public final class PlayerConnection implements Listener {
             player.setOp(true);
         }
 
-        /*try {
-            player.setPlayerProfile(SkinDownloader.getProfile(player.getUniqueId()));
-            SkinDownloader.removeProfile(player.getUniqueId());
-        } catch (Exception ignored) {
-        }*/
+        final Server server = Bukkit.getServer();
+
+
+        if (!server.getOnlineMode()) {
+            SkinManager.applySkin(player, player.getName(), false);
+        }
     }
 
     @EventHandler
diff --git a/src/main/java/pw/kaboom/extras/skin/SkinData.java b/src/main/java/pw/kaboom/extras/skin/SkinData.java
new file mode 100644
index 0000000..b78fffc
--- /dev/null
+++ b/src/main/java/pw/kaboom/extras/skin/SkinData.java
@@ -0,0 +1,5 @@
+package pw.kaboom.extras.skin;
+
+public record SkinData(String texture, String signature) {
+
+}
\ No newline at end of file
diff --git a/src/main/java/pw/kaboom/extras/skin/SkinManager.java b/src/main/java/pw/kaboom/extras/skin/SkinManager.java
new file mode 100644
index 0000000..be76dea
--- /dev/null
+++ b/src/main/java/pw/kaboom/extras/skin/SkinManager.java
@@ -0,0 +1,159 @@
+package pw.kaboom.extras.skin;
+
+import com.google.gson.Gson;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.List;
+import java.util.UUID;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import com.destroystokyo.paper.profile.PlayerProfile;
+import com.destroystokyo.paper.profile.ProfileProperty;
+
+import net.kyori.adventure.text.Component;
+
+import org.bukkit.scheduler.BukkitScheduler;
+import pw.kaboom.extras.Main;
+import pw.kaboom.extras.skin.response.ProfileResponse;
+import pw.kaboom.extras.skin.response.SkinResponse;
+
+public final class SkinManager {
+    private static final HttpClient httpClient = HttpClient.newHttpClient();
+    private static final Gson GSON = new Gson();
+    private static final ExecutorService executorService = Executors
+        .newCachedThreadPool();
+
+    public static void resetSkin(final Player player, final boolean shouldSendMessage) {
+        executorService.submit(() -> {
+            final PlayerProfile playerProfile = player.getPlayerProfile();
+            playerProfile.removeProperty("textures");
+
+            final BukkitScheduler bukkitScheduler = Bukkit.getScheduler();
+            final Main plugin = JavaPlugin.getPlugin(Main.class);
+
+            bukkitScheduler.runTask(plugin, () -> player.setPlayerProfile(playerProfile));
+
+            if(!shouldSendMessage) {
+                return;
+            }
+
+            player.sendMessage(Component.text("Successfully removed your skin"));
+        });
+    }
+
+    public static void applySkin(final Player player, final String name,
+        final boolean shouldSendMessage) {
+        executorService.submit(() -> {
+            final PlayerProfile profile = player.getPlayerProfile();
+            final SkinData skinData;
+
+            try {
+                skinData = getSkinData(name).get();
+            } catch (Exception e) {
+                if(!shouldSendMessage) {
+                    return;
+                }
+
+                player.sendMessage(Component.text("A player with that username doesn't exist"));
+                return;
+            }
+
+            final String texture = skinData.texture();
+            final String signature = skinData.signature();
+            profile.setProperty(new ProfileProperty("textures", texture, signature));
+
+            final BukkitScheduler bukkitScheduler = Bukkit.getScheduler();
+            final Main plugin = JavaPlugin.getPlugin(Main.class);
+
+            bukkitScheduler.runTask(plugin,
+                () -> player.setPlayerProfile(profile));
+
+
+            if(!shouldSendMessage) {
+                return;
+            }
+
+            player.sendMessage(Component.text("Successfully set your skin to ")
+                .append(Component.text(name))
+                .append(Component.text("'s")));
+        });
+    }
+
+    public static CompletableFuture<SkinData> getSkinData(final String playerName) {
+        return CompletableFuture.supplyAsync(() -> {
+            final UUID uuid;
+            try {
+                uuid = getUUID(playerName).get();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+
+            try {
+                return getSkinData(uuid).get();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }, executorService);
+    }
+
+    public static CompletableFuture<SkinData> getSkinData(final UUID uuid) {
+        return CompletableFuture.supplyAsync(() -> {
+            final SkinResponse response = sendRequestForJSON(
+                "https://sessionserver.mojang.com/session/minecraft/profile/"
+                + uuid + "?unsigned=false", SkinResponse.class);
+
+            final List<ProfileProperty> properties = response.properties();
+
+            for (ProfileProperty property : properties) {
+                if(!property.getName().equals("textures")) {
+                    continue;
+                }
+
+                return new SkinData(property.getValue(), property.getSignature());
+            }
+
+            throw new RuntimeException("No textures property");
+        }, executorService);
+    }
+
+    private static <T> T sendRequestForJSON(String url, Class<T> clazz) {
+        final HttpRequest request = HttpRequest.newBuilder()
+            .GET()
+            .uri(URI.create(url))
+            .build();
+
+        final HttpResponse<String> response;
+
+        try {
+            response = httpClient.send(request, BodyHandlers.ofString());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        return GSON.fromJson(response.body(), clazz);
+    }
+
+    private static CompletableFuture<UUID> getUUID(final String playerName) {
+        return CompletableFuture.supplyAsync(() -> {
+            final ProfileResponse parsedResponse = sendRequestForJSON
+                ("https://api.mojang.com/users/profiles/minecraft/" + playerName,
+                ProfileResponse.class);
+
+            final String dashedUuid = parsedResponse
+                .id()
+                .replaceAll("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5");
+
+            return UUID.fromString(dashedUuid);
+        }, executorService);
+    }
+}
diff --git a/src/main/java/pw/kaboom/extras/skin/response/ProfileResponse.java b/src/main/java/pw/kaboom/extras/skin/response/ProfileResponse.java
new file mode 100644
index 0000000..70da361
--- /dev/null
+++ b/src/main/java/pw/kaboom/extras/skin/response/ProfileResponse.java
@@ -0,0 +1,20 @@
+package pw.kaboom.extras.skin.response;
+
+public class ProfileResponse {
+
+	public ProfileResponse(String name, String id) {
+		this.name = name;
+		this.id = id;
+	}
+
+	private final String name;
+	private final String id;
+
+	public String name() {
+		return name;
+	}
+
+	public String id() {
+		return id;
+	}
+}
diff --git a/src/main/java/pw/kaboom/extras/skin/response/SkinResponse.java b/src/main/java/pw/kaboom/extras/skin/response/SkinResponse.java
new file mode 100644
index 0000000..340b8de
--- /dev/null
+++ b/src/main/java/pw/kaboom/extras/skin/response/SkinResponse.java
@@ -0,0 +1,32 @@
+package pw.kaboom.extras.skin.response;
+
+import com.destroystokyo.paper.profile.ProfileProperty;
+import java.util.List;
+import java.util.Objects;
+
+public final class SkinResponse {
+
+	private final String id;
+	private final String name;
+	private final List<ProfileProperty> properties;
+
+	public SkinResponse(String id, String name, List<ProfileProperty> properties) {
+		this.id = id;
+		this.name = name;
+		this.properties = properties;
+	}
+
+	public String id() {
+		return id;
+	}
+
+	public String name() {
+		return name;
+	}
+
+	public List<ProfileProperty> properties() {
+		return properties;
+	}
+
+
+}