diff --git a/Cargo.lock b/Cargo.lock
index 85e7561..d72361f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -802,6 +802,11 @@ name = "matches"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "md5"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "memchr"
 version = "0.1.11"
@@ -1193,6 +1198,7 @@ dependencies = [
  "iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "lewton 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "md5 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "metaflac 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "mount 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "mp3-duration 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1206,6 +1212,7 @@ dependencies = [
  "rustfm-scrobble 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "secure-session 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde-xml-rs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "simplelog 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1500,6 +1507,16 @@ name = "serde"
 version = "1.0.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "serde-xml-rs"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "serde_derive"
 version = "1.0.24"
@@ -1927,6 +1944,14 @@ name = "xdg"
 version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "xml-rs"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [metadata]
 "checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45"
 "checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a"
@@ -2025,6 +2050,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d06ff7ff06f729ce5f4e227876cb88d10bc59cd4ae1e09fbb2bde15c850dc21"
 "checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
 "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
+"checksum md5 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "995999bcecec06dff8499bfafab45119c1d33a57d75a705b429d6d49f38c2f40"
 "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
 "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
 "checksum metaflac 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1839a57e30c651fb9647d1c140dcda407282a2228cddb25a21c1708645621219"
@@ -2096,6 +2122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum sequence_trie 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c915714ca833b1d4d6b8f6a9d72a3ff632fe45b40a8d184ef79c81bec6327eed"
 "checksum serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
 "checksum serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1c57ab4ec5fa85d08aaf8ed9245899d9bbdd66768945b21113b84d5f595cb6a1"
+"checksum serde-xml-rs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0c06881f4313eec67d4ecfcd8e14339f6042cfc0de4b1bd3ceae74c29d597f68"
 "checksum serde_derive 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "02c92ea07b6e49b959c1481804ebc9bfd92d3c459f1274c9a9546829e42a66ce"
 "checksum serde_derive_internals 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75c6aac7b99801a16db5b40b7bf0d7e4ba16e76fbf231e32a4677f271cac0603"
 "checksum serde_json 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "67f7d2e9edc3523a9c8ec8cd6ec481b3a27810aafee3e625d311febd3e656b4c"
@@ -2150,3 +2177,4 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum wrapped-vec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "06c29bb4abe93d1c8ef79b60f270d0efcaa6c5c97aaaaaaa0d477ea72f5f9e45"
 "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
 "checksum xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a66b7c2281ebde13cf4391d70d4c7e5946c3c25e72a7b859ca8f677dcd0b0c61"
+"checksum xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7ec6c39eaa68382c8e31e35239402c0a9489d4141a8ceb0c716099a0b515b562"
diff --git a/Cargo.toml b/Cargo.toml
index 97e2e1b..7168402 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,6 +20,7 @@ image = "0.15.0"
 iron = "0.5.1"
 rustfm-scrobble = "0.9.1"
 lewton = "0.6.2"
+md5 = "0.4.0"
 metaflac = "0.1.8"
 mount = "0.3.0"
 mp3-duration = "0.1.0"
@@ -33,6 +34,7 @@ secure-session = "0.2.0"
 serde = "1.0"
 serde_derive = "1.0"
 serde_json = "1.0"
+serde-xml-rs = "0.2.1"
 staticfile = "0.4.0"
 toml = "0.4.5"
 typemap = "0.3"
diff --git a/src/api.rs b/src/api.rs
index a1817b7..fa189dc 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -204,6 +204,12 @@ fn get_endpoints(db: Arc<DB>, index_channel: Arc<Mutex<Sender<index::Command>>>)
 
 			auth_api_mount.mount("/playlist/", playlist_router);
 		}
+		{
+			let db = db.clone();
+			auth_api_mount.mount("/lastfm/auth/", move |request: &mut Request| {
+				self::lastfm_auth(request, db.deref())
+			});
+		}
 		{
 			let db = db.clone();
 			auth_api_mount.mount("/lastfm/now_playing/", move |request: &mut Request| {
@@ -698,6 +704,22 @@ fn delete_playlist(request: &mut Request, db: &DB) -> IronResult<Response> {
 	Ok(Response::with(status::Ok))
 }
 
+fn lastfm_auth(request: &mut Request, db: &DB) -> IronResult<Response> {
+	let input = request.get_ref::<params::Params>().unwrap();
+	let username = match input.find(&["username"]) {
+		Some(&params::Value::String(ref username)) => username.clone(),
+		_ => return Err(Error::from(ErrorKind::MissingUsername).into()),
+	};
+	let token = match input.find(&["token"]) {
+		Some(&params::Value::String(ref token)) => token.clone(),
+		_ => return Err(Error::from(ErrorKind::MissingPassword).into()),
+	};
+
+	lastfm::auth(db, &username, &token)?;
+
+	Ok(Response::with(status::Ok))
+}
+
 fn lastfm_now_playing(request: &mut Request, db: &DB) -> IronResult<Response> {
 	let username = match request.extensions.get::<SessionKey>() {
 		Some(s) => s.username.clone(),
diff --git a/src/config.rs b/src/config.rs
index 53bf4ed..7cb62bb 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -26,10 +26,7 @@ pub struct MiscSettings {
 }
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct Preferences {
-	pub lastfm_username: Option<String>,
-	pub lastfm_password: Option<String>,
-}
+pub struct Preferences {}
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub struct ConfigUser {
@@ -256,31 +253,15 @@ pub fn amend<T>(db: &T, new_config: &Config) -> Result<()>
 	Ok(())
 }
 
-pub fn read_preferences<T>(db: &T, username: &str) -> Result<Preferences>
+pub fn read_preferences<T>(_: &T, _: &str) -> Result<Preferences>
 	where T: ConnectionSource
 {
-	use self::users::dsl::*;
-	let connection = db.get_connection();
-	let (read_lastfm_username, read_lastfm_password) = users
-		.select((lastfm_username, lastfm_password))
-		.filter(name.eq(username))
-		.get_result(connection.deref())?;
-	Ok(Preferences {
-	       lastfm_username: read_lastfm_username,
-	       lastfm_password: read_lastfm_password,
-	   })
+	Ok(Preferences {})
 }
 
-pub fn write_preferences<T>(db: &T, username: &str, preferences: &Preferences) -> Result<()>
+pub fn write_preferences<T>(_: &T, _: &str, _: &Preferences) -> Result<()>
 	where T: ConnectionSource
 {
-	use self::users::dsl::*;
-	let connection = db.get_connection();
-	diesel::update(users)
-		.set((lastfm_username.eq(&preferences.lastfm_username),
-		      lastfm_password.eq(&preferences.lastfm_password)))
-		.filter(name.eq(username))
-		.execute(connection.deref())?;
 	Ok(())
 }
 
@@ -490,14 +471,7 @@ fn test_preferences_read_write() {
 	};
 	amend(&db, &initial_config).unwrap();
 
-	let old_preferences = read_preferences(&db, "Teddy🐻").unwrap();
-	assert_eq!(old_preferences.lastfm_username, None);
-	assert_eq!(old_preferences.lastfm_password, None);
-
-	let new_preferences = Preferences {
-		lastfm_username: Some("🐻FM".into()),
-		lastfm_password: Some("Secret🐻Secret".into()),
-	};
+	let new_preferences = Preferences {};
 	write_preferences(&db, "Teddy🐻", &new_preferences).unwrap();
 
 	let read_preferences = read_preferences(&db, "Teddy🐻").unwrap();
diff --git a/src/db/migrations/20180303211100_add_last_fm_credentials/up.sql b/src/db/migrations/20180303211100_add_last_fm_credentials/up.sql
index 3a01018..40cc2e1 100644
--- a/src/db/migrations/20180303211100_add_last_fm_credentials/up.sql
+++ b/src/db/migrations/20180303211100_add_last_fm_credentials/up.sql
@@ -1,2 +1,2 @@
 ALTER TABLE users ADD COLUMN lastfm_username TEXT;
-ALTER TABLE users ADD COLUMN lastfm_password TEXT;
+ALTER TABLE users ADD COLUMN lastfm_session_key TEXT;
diff --git a/src/db/schema.sqlite b/src/db/schema.sqlite
index 74c0758..9bea424 100644
Binary files a/src/db/schema.sqlite and b/src/db/schema.sqlite differ
diff --git a/src/errors.rs b/src/errors.rs
index 5fd947f..2d0cd09 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -54,6 +54,8 @@ error_chain! {
         MissingPlaylistName {}
         EncodingError {}
         MissingLastFMCredentials {}
+        LastFMAuthError {}
+        LastFMDeserializationError {}
     }
 }
 
diff --git a/src/index.rs b/src/index.rs
index 6deef01..56df257 100644
--- a/src/index.rs
+++ b/src/index.rs
@@ -768,7 +768,7 @@ fn test_recent() {
 
 #[test]
 fn test_get_song() {
-	let db = db::_get_test_db("recent.sqlite");
+	let db = db::_get_test_db("get_song.sqlite");
 	update(&db).unwrap();
 
 	let mut song_path = PathBuf::new();
diff --git a/src/lastfm.rs b/src/lastfm.rs
index 4a3e5e9..c259181 100644
--- a/src/lastfm.rs
+++ b/src/lastfm.rs
@@ -1,16 +1,53 @@
+use md5;
+use reqwest;
 use rustfm_scrobble::{Scrobbler, Scrobble};
+use serde_xml_rs::deserialize;
+use std::collections::HashMap;
+use std::io::Read;
 use std::path::Path;
 
 use db::ConnectionSource;
-use errors::*;
+use errors;
 use index;
 use user;
 use vfs::VFSSource;
 
 const LASTFM_API_KEY: &str = "02b96c939a2b451c31dfd67add1f696e";
 const LASTFM_API_SECRET: &str = "0f25a80ceef4b470b5cb97d99d4b3420";
+const LASTFM_API_ROOT: &str = "http://ws.audioscrobbler.com/2.0/";
 
-fn scrobble_from_path<T>(db: &T, track: &Path) -> Result<Scrobble>
+#[derive(Debug, Deserialize)]
+struct AuthResponseSessionName {
+	#[serde(rename = "$value")]
+	pub body: String,
+}
+
+#[derive(Debug, Deserialize)]
+struct AuthResponseSessionKey {
+	#[serde(rename = "$value")]
+	pub body: String,
+}
+
+#[derive(Debug, Deserialize)]
+struct AuthResponseSessionSubscriber {
+	#[serde(rename = "$value")]
+	pub body: i32,
+}
+
+#[derive(Debug, Deserialize)]
+struct AuthResponseSession {
+	pub name: AuthResponseSessionName,
+	pub key: AuthResponseSessionKey,
+	pub subscriber: AuthResponseSessionSubscriber,
+}
+
+#[derive(Debug, Deserialize)]
+struct AuthResponse {
+	pub status: String,
+	pub session: AuthResponseSession,
+}
+
+fn scrobble_from_path<T>(db: &T, track: &Path) -> Result<Scrobble, errors::Error>
 	where T: ConnectionSource + VFSSource
 {
 	let song = index::get_song(db, track)?;
@@ -19,26 +56,91 @@ fn scrobble_from_path<T>(db: &T, track: &Path) -> Result<Scrobble>
 	                 song.album.unwrap_or("".into())))
 }
 
-pub fn scrobble<T>(db: &T, username: &str, track: &Path) -> Result<()>
+pub fn auth<T>(db: &T, username: &str, token: &str) -> Result<(), errors::Error>
+	where T: ConnectionSource + VFSSource
+{
+	let mut params = HashMap::new();
+	params.insert("token".to_string(), token.to_string());
+	params.insert("api_key".to_string(), LASTFM_API_KEY.to_string());
+	let mut response = match api_request("auth.getSession", &params) {
+		Ok(r) => r,
+		Err(_) => bail!(errors::ErrorKind::LastFMAuthError),
+	};
+
+	let mut body = String::new();
+	response.read_to_string(&mut body)?;
+	if !response.status().is_success() {
+		bail!(errors::ErrorKind::LastFMAuthError)
+	}
+
+	let auth_response: AuthResponse = match deserialize(body.as_bytes()) {
+		Ok(d) => d,
+		Err(_) => bail!(errors::ErrorKind::LastFMDeserializationError)
+	};
+
+	user::set_lastfm_session_key(db, username, &auth_response.session.key.body)
+}
+
+pub fn scrobble<T>(db: &T, username: &str, track: &Path) -> Result<(), errors::Error>
 	where T: ConnectionSource + VFSSource
 {
 	let mut scrobbler = Scrobbler::new(LASTFM_API_KEY.into(), LASTFM_API_SECRET.into());
 	let scrobble = scrobble_from_path(db, track)?;
-	let (lastfm_username, lastfm_password) = user::get_lastfm_credentials(db, username)?;
-	scrobbler
-		.authenticate_with_password(lastfm_username, lastfm_password)?;
+	let auth_token = user::get_lastfm_session_key(db, username)?;
+	scrobbler.authenticate_with_session_key(auth_token);
 	scrobbler.scrobble(scrobble)?;
 	Ok(())
 }
 
-pub fn now_playing<T>(db: &T, username: &str, track: &Path) -> Result<()>
+pub fn now_playing<T>(db: &T, username: &str, track: &Path) -> Result<(), errors::Error>
 	where T: ConnectionSource + VFSSource
 {
 	let mut scrobbler = Scrobbler::new(LASTFM_API_KEY.into(), LASTFM_API_SECRET.into());
 	let scrobble = scrobble_from_path(db, track)?;
-	let (lastfm_username, lastfm_password) = user::get_lastfm_credentials(db, username)?;
-	scrobbler
-		.authenticate_with_password(lastfm_username, lastfm_password)?;
+	let auth_token = user::get_lastfm_session_key(db, username)?;
+	scrobbler.authenticate_with_session_key(auth_token);
 	scrobbler.now_playing(scrobble)?;
 	Ok(())
 }
+
+fn api_request(method: &str, params: &HashMap<String, String>) -> Result<reqwest::Response, reqwest::Error>
+{
+	let mut url = LASTFM_API_ROOT.to_string();
+	url.push_str("?");
+
+	url.push_str(&format!("method={}&", method));
+	for (k, v) in params.iter()
+	{
+		url.push_str(&format!("{}={}&", k, v));
+	}
+	let api_signature = get_signature(method, params);
+	url.push_str(&format!("api_sig={}", api_signature));
+
+	let client = reqwest::Client::new()?;
+	let request = client.get(url.as_str());
+	request.send()
+}
+
+fn get_signature(method: &str, params: &HashMap<String, String>) -> String
+{
+	let mut signature_data = params.clone();
+	signature_data.insert("method".to_string(), method.to_string());
+
+	let mut param_names = Vec::new();
+	for param_name in signature_data.keys()
+	{
+		param_names.push(param_name);
+	}
+	param_names.sort();
+
+	let mut signature = String::new();
+	for param_name in param_names
+	{
+		signature.push_str((param_name.to_string() + signature_data[param_name].as_str()).as_str())
+	}
+
+	signature.push_str(LASTFM_API_SECRET);
+
+	let digest = md5::compute(signature.as_bytes());
+	format!("{:X}", digest)
+}
diff --git a/src/main.rs b/src/main.rs
index 5a619b5..a232708 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -17,6 +17,7 @@ extern crate id3;
 extern crate image;
 extern crate iron;
 extern crate lewton;
+extern crate md5;
 extern crate metaflac;
 extern crate mount;
 extern crate mp3_duration;
@@ -32,6 +33,7 @@ extern crate serde;
 #[macro_use]
 extern crate serde_derive;
 extern crate serde_json;
+extern crate serde_xml_rs;
 extern crate staticfile;
 extern crate toml;
 extern crate typemap;
diff --git a/src/user.rs b/src/user.rs
index 774125f..394d3f9 100644
--- a/src/user.rs
+++ b/src/user.rs
@@ -93,17 +93,28 @@ pub fn is_admin<T>(db: &T, username: &str) -> Result<bool>
 	Ok(is_admin != 0)
 }
 
-pub fn get_lastfm_credentials<T>(db: &T, username: &str) -> Result<(String, String)>
+pub fn set_lastfm_session_key<T>(db: &T, username: &str, token: &str) -> Result<()>
 	where T: ConnectionSource
 {
 	use db::users::dsl::*;
 	let connection = db.get_connection();
-	let credentials = users
+	diesel::update(users.filter(name.eq(username)))
+		.set(lastfm_session_key.eq(token))
+		.execute(connection.deref())?;
+	Ok(())
+}
+
+pub fn get_lastfm_session_key<T>(db: &T, username: &str) -> Result<String>
+	where T: ConnectionSource
+{
+	use db::users::dsl::*;
+	let connection = db.get_connection();
+	let token = users
 		.filter(name.eq(username))
-		.select((lastfm_username, lastfm_password))
+		.select(lastfm_session_key)
 		.get_result(connection.deref())?;
-	match credentials {
-		(Some(u), Some(p)) => Ok((u, p)),
+	match token {
+		Some(t) => Ok(t),
 		_ => bail!(ErrorKind::MissingLastFMCredentials),
 	}
 }