diff --git a/README.md b/README.md
index acbabdd2f00d38863bae70bf7debd991c545f4e0..dd03edd8a6fd316dc1c60d5fbd69331710510046 100644
--- a/README.md
+++ b/README.md
@@ -179,11 +179,13 @@ Supported options:
 
 | Provider |       Option | Notes                                                                                                                                  |
 |---------:|-------------:|----------------------------------------------------------------------------------------------------------------------------------------|
-| Google   |       domain | If this is unset then you must whitelist individual email addresses using `users_whitelist`.                                           |
 | Github   | organization | If this is unset then you must whitelist individual users using `users_whitelist`. The oauth client and secrets should be issued by the specified organization. |
-| Gitlab   | siteurl | Optional. The url of the Gitlab site. Default: `https://gitlab.com/api/v3/` |
 | Gitlab   | allusers | Allow all valid users to get signed keys. Only allowed if siteurl set. |
 | Gitlab   | group | If `allusers` and this are unset then you must whitelist individual users using `users_whitelist`. Otherwise the user must be a member of this group. |
+| Gitlab   | siteurl | Optional. The url of the Gitlab site. Default: `https://gitlab.com/api/v3/` |
+| Google   |       domain | If this is unset then you must whitelist individual email addresses using `users_whitelist`.                                           |
+| Microsoft | groups | Comma separated list of valid groups. |
+| Microsoft | tenant | The domain name of the Office 365 account. |
 
 ## ssh
 - `signing_key`: string. Path to the signing ssh private key you created earlier. See the [note](#a-note-on-files) on files above.
diff --git a/cmd/cashier/main.go b/cmd/cashier/main.go
index c62a61695c55a9294f81b9c42743127f41adfdbb..d341b01a8f2c936021e1e14e8892390ee11c540a 100644
--- a/cmd/cashier/main.go
+++ b/cmd/cashier/main.go
@@ -53,7 +53,7 @@ func main() {
 	fmt.Print("Enter token: ")
 	scanner := bufio.NewScanner(os.Stdin)
 	var buffer bytes.Buffer
-	for scanner.Scan(); scanner.Text() == ".\n"; scanner.Scan() {
+	for scanner.Scan(); scanner.Text() != "."; scanner.Scan() {
 		buffer.WriteString(scanner.Text())
 	}
 	tokenBytes, err := base64.StdEncoding.DecodeString(buffer.String())
diff --git a/server/auth/microsoft/microsoft.go b/server/auth/microsoft/microsoft.go
index a66df4046129c62fddac1a450bc4eac338796343..cbba53458d80052472b4e26feb15e647439be046 100644
--- a/server/auth/microsoft/microsoft.go
+++ b/server/auth/microsoft/microsoft.go
@@ -1,11 +1,12 @@
 package microsoft
 
 import (
+	"encoding/json"
 	"errors"
+	"log"
 	"net/http"
 	"strings"
 
-	//"github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac"
 	"github.com/nsheridan/cashier/server/auth"
 	"github.com/nsheridan/cashier/server/config"
 	"github.com/nsheridan/cashier/server/metrics"
@@ -23,6 +24,7 @@ const (
 type Config struct {
 	config    *oauth2.Config
 	tenant    string
+	groups    map[string]bool
 	whitelist map[string]bool
 }
 
@@ -30,13 +32,19 @@ var _ auth.Provider = (*Config)(nil)
 
 // New creates a new Microsoft provider from a configuration.
 func New(c *config.Auth) (*Config, error) {
-	uw := make(map[string]bool)
+	whitelist := make(map[string]bool)
 	for _, u := range c.UsersWhitelist {
-		uw[u] = true
+		whitelist[u] = true
 	}
-	if c.ProviderOpts["tenant"] == "" && len(uw) == 0 {
+	if c.ProviderOpts["tenant"] == "" && len(whitelist) == 0 {
 		return nil, errors.New("either Office 365 tenant or users whitelist must be specified")
 	}
+	groupMap := make(map[string]bool)
+	if groups, ok := c.ProviderOpts["groups"]; ok {
+		for _, group := range strings.Split(groups, ",") {
+			groupMap[strings.Trim(group, " ")] = true
+		}
+	}
 
 	return &Config{
 		config: &oauth2.Config{
@@ -44,10 +52,11 @@ func New(c *config.Auth) (*Config, error) {
 			ClientSecret: c.OauthClientSecret,
 			RedirectURL:  c.OauthCallbackURL,
 			Endpoint:     microsoft.AzureADEndpoint(c.ProviderOpts["tenant"]),
-			Scopes:       []string{"user.Read"},
+			Scopes:       []string{"user.Read.All", "Directory.Read.All"},
 		},
 		tenant:    c.ProviderOpts["tenant"],
-		whitelist: uw,
+		whitelist: whitelist,
+		groups:    groupMap,
 	}, nil
 }
 
@@ -56,6 +65,101 @@ func (c *Config) newClient(token *oauth2.Token) *http.Client {
 	return c.config.Client(oauth2.NoContext, token)
 }
 
+// Gets a response for an graph api call.
+func (c *Config) getDocument(token *oauth2.Token, pathElements ...string) map[string]interface{} {
+	client := c.newClient(token)
+	var url strings.Builder
+	url.WriteString("https://graph.microsoft.com/v1.0")
+	for _, pathElement := range pathElements {
+		url.WriteString(pathElement)
+	}
+	resp, err := client.Get(url.String())
+	if err != nil {
+		log.Printf("Failed to get response for %s (%s)", url.String(), err)
+		return nil
+	}
+	defer resp.Body.Close()
+	var document map[string]interface{}
+	if err := json.NewDecoder(resp.Body).Decode(&document); err != nil {
+		log.Printf("Failed to get document for %s (%s)", url.String(), err)
+		return nil
+	}
+	return document
+}
+
+// Get info from the "/me" endpoint of the Microsoft Graph API (MSG-API).
+// https://developer.microsoft.com/en-us/graph/docs/concepts/v1-overview
+func (c *Config) getMe(token *oauth2.Token, item string) string {
+	document := c.getDocument(token, "/me")
+	if len(document) == 0 {
+		log.Printf("Document empty for getMe(%s)", item)
+		return ""
+	}
+	if value, ok := document[item].(string); ok {
+		return value
+	}
+	log.Printf("Couldn't find item for getMe(%s)", item)
+	return ""
+}
+
+// Check against verified domains from "/organization" endpoint of MSG-API.
+func (c *Config) verifyTenant(token *oauth2.Token) bool {
+	document := c.getDocument(token, "/organization")
+	if len(document) == 0 {
+		log.Printf("Document empty for verifyTenant")
+		return false
+	}
+	var value []interface{}
+	var ok bool
+	if value, ok = document["value"].([]interface{}); !ok {
+		log.Printf("No value for verifyTenant")
+		return false
+	}
+	for _, valueEntry := range value {
+		if value, ok = valueEntry.(map[string]interface{})["verifiedDomains"].([]interface{}); !ok {
+			continue
+		}
+		for _, val := range value {
+			domain := val.(map[string]interface{})["name"].(string)
+			if domain == c.tenant {
+				return true
+			}
+		}
+	}
+	log.Printf("No valid tenant for verifyTenant")
+	return false
+}
+
+// Check against groups from /users/{id}/memberOf endpoint of MSG-API.
+func (c *Config) verifyGroups(token *oauth2.Token) bool {
+	id := c.getMe(token, "id")
+	if id == "" {
+		log.Printf("Empty id for verifyGroup")
+		return false
+	}
+	document := c.getDocument(token, "/users/", id, "/memberOf")
+	if len(document) == 0 {
+		log.Printf("Empty document for verifyGroup")
+		return false
+	}
+	var value []interface{}
+	var ok bool
+	if value, ok = document["value"].([]interface{}); !ok {
+		log.Printf("Missing value for verifyGroup: %v", document)
+		return false
+	}
+	for _, valueEntry := range value {
+		if group, ok := valueEntry.(map[string]interface{})["displayName"].(string); ok {
+			log.Printf("Checking %s with (%s) in verifyGroup", group, c.groups)
+			if c.groups[group] {
+				return true
+			}
+		}
+	}
+	log.Printf("No valid group for verifyGroup")
+	return false
+}
+
 // Name returns the name of the provider.
 func (c *Config) Name() string {
 	return name
@@ -70,7 +174,15 @@ func (c *Config) Valid(token *oauth2.Token) bool {
 		return false
 	}
 	metrics.M.AuthValid.WithLabelValues("microsoft").Inc()
-	return true
+	if c.tenant != "" {
+		if c.verifyTenant(token) {
+			if len(c.groups) > 0 {
+				return c.verifyGroups(token)
+			}
+			return true
+		}
+	}
+	return false
 }
 
 // Revoke disables the access token.
@@ -91,29 +203,12 @@ func (c *Config) Exchange(code string) (*oauth2.Token, error) {
 	if err == nil {
 		metrics.M.AuthExchange.WithLabelValues("microsoft").Inc()
 	}
-	/*
-		Need to get the User Principle Name here.  This can be done as follows.
-		1. id_token = t.Extra("id_token")  // yields JWT claim.
-		2. claim = jwt.Parse(id_token, some function?)
-		3. claim.Something?("upn")
-
-		Or maybe there are these operations on the signed in user:
-		https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/signed-in-user-operations
-		How to do this via the Azure SDK for Go: https://github.com/Azure/azure-rest-api-specs/issues/2647
-
-		Reference:
-		Azure Oauth flow: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code
-		OAuth token: https://godoc.org/golang.org/x/oauth2#Token
-		JWT lib: https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac
-	*/
 	return t, err
 }
 
 // Email retrieves the email address of the user.
 func (c *Config) Email(token *oauth2.Token) string {
-	//uclient := graphrbac.NewUsersClient("myorganization")
-
-	return "nobody@nowhere"
+	return c.getMe(token, "mail")
 }
 
 // Username retrieves the username portion of the user's email address.