diff --git a/src/main/java/org/whispersystems/textsecuregcm/auth/DeviceAuthenticator.java b/src/main/java/org/whispersystems/textsecuregcm/auth/DeviceAuthenticator.java
new file mode 100644
index 000000000..bb68abea3
--- /dev/null
+++ b/src/main/java/org/whispersystems/textsecuregcm/auth/DeviceAuthenticator.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (C) 2013 Open WhisperSystems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.whispersystems.textsecuregcm.auth;
+
+import com.google.common.base.Optional;
+import com.yammer.dropwizard.auth.AuthenticationException;
+import com.yammer.dropwizard.auth.Authenticator;
+import com.yammer.dropwizard.auth.basic.BasicCredentials;
+import com.yammer.metrics.Metrics;
+import com.yammer.metrics.core.Meter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.whispersystems.textsecuregcm.storage.Device;
+import org.whispersystems.textsecuregcm.storage.AccountsManager;
+
+import java.util.concurrent.TimeUnit;
+
+public class DeviceAuthenticator implements Authenticator {
+
+ private final Meter authenticationFailedMeter = Metrics.newMeter(DeviceAuthenticator.class,
+ "authentication", "failed",
+ TimeUnit.MINUTES);
+
+ private final Meter authenticationSucceededMeter = Metrics.newMeter(DeviceAuthenticator.class,
+ "authentication", "succeeded",
+ TimeUnit.MINUTES);
+
+ private final Logger logger = LoggerFactory.getLogger(DeviceAuthenticator.class);
+
+ private final AccountsManager accountsManager;
+
+ public DeviceAuthenticator(AccountsManager accountsManager) {
+ this.accountsManager = accountsManager;
+ }
+
+ @Override
+ public Optional authenticate(BasicCredentials basicCredentials)
+ throws AuthenticationException
+ {
+ AuthorizationHeader authorizationHeader;
+ try {
+ authorizationHeader = AuthorizationHeader.fromUserAndPassword(basicCredentials.getUsername(), basicCredentials.getPassword());
+ } catch (InvalidAuthorizationHeaderException iahe) {
+ return Optional.absent();
+ }
+ Optional device = accountsManager.get(authorizationHeader.getNumber(), authorizationHeader.getDeviceId());
+
+ if (!device.isPresent()) {
+ return Optional.absent();
+ }
+
+ if (device.get().getAuthenticationCredentials().verify(basicCredentials.getPassword())) {
+ authenticationSucceededMeter.mark();
+ return device;
+ }
+
+ authenticationFailedMeter.mark();
+ return Optional.absent();
+ }
+}