diff --git a/apps.go b/apps.go
index 301fa6f..b03f9b1 100644
--- a/apps.go
+++ b/apps.go
@@ -18,7 +18,9 @@ type AppConfig struct {
 	// Where the user should be redirected after authorization (for no redirect, use urn:ietf:wg:oauth:2.0:oob)
 	RedirectURIs string
 
-	// This can be a space-separated list of the following items: "read", "write" and "follow".
+	// This can be a space-separated list of items listed on the /settings/applications/new page of any Mastodon
+	// instance. "read", "write", and "follow" are top-level scopes that include all the permissions of the more
+	// specific scopes like "read:favourites", "write:statuses", and "write:follows".
 	Scopes string
 
 	// Optional.
@@ -31,6 +33,9 @@ type Application struct {
 	RedirectURI  string `json:"redirect_uri"`
 	ClientID     string `json:"client_id"`
 	ClientSecret string `json:"client_secret"`
+
+	// AuthURI is not part of the Mastodon API; it is generated by go-mastodon.
+	AuthURI string `json:"auth_uri,omitempty"`
 }
 
 // RegisterApp returns the mastodon application.
@@ -73,5 +78,19 @@ func RegisterApp(ctx context.Context, appConfig *AppConfig) (*Application, error
 		return nil, err
 	}
 
+	u, err = url.Parse(appConfig.Server)
+	if err != nil {
+		return nil, err
+	}
+	u.Path = path.Join(u.Path, "/oauth/authorize")
+	u.RawQuery = url.Values{
+		"scope":         {appConfig.Scopes},
+		"response_type": {"code"},
+		"redirect_uri":  {app.RedirectURI},
+		"client_id":     {app.ClientID},
+	}.Encode()
+
+	app.AuthURI = u.String()
+
 	return &app, nil
 }
diff --git a/mastodon.go b/mastodon.go
index 68db1ba..a856606 100644
--- a/mastodon.go
+++ b/mastodon.go
@@ -130,14 +130,34 @@ func NewClient(config *Config) *Client {
 
 // Authenticate get access-token to the API.
 func (c *Client) Authenticate(ctx context.Context, username, password string) error {
-	params := url.Values{}
-	params.Set("client_id", c.config.ClientID)
-	params.Set("client_secret", c.config.ClientSecret)
-	params.Set("grant_type", "password")
-	params.Set("username", username)
-	params.Set("password", password)
-	params.Set("scope", "read write follow")
+	params := url.Values{
+		"client_id":     {c.config.ClientID},
+		"client_secret": {c.config.ClientSecret},
+		"grant_type":    {"password"},
+		"username":      {username},
+		"password":      {password},
+		"scope":         {"read write follow"},
+	}
 
+	return c.authenticate(ctx, params)
+}
+
+// AuthenticateToken logs in using a grant token returned by Application.AuthURI.
+//
+// redirectURI should be the same as Application.RedirectURI.
+func (c *Client) AuthenticateToken(ctx context.Context, authCode, redirectURI string) error {
+	params := url.Values{
+		"client_id":     {c.config.ClientID},
+		"client_secret": {c.config.ClientSecret},
+		"grant_type":    {"authorization_code"},
+		"code":          {authCode},
+		"redirect_uri":  {redirectURI},
+	}
+
+	return c.authenticate(ctx, params)
+}
+
+func (c *Client) authenticate(ctx context.Context, params url.Values) error {
 	u, err := url.Parse(c.config.Server)
 	if err != nil {
 		return err
@@ -160,9 +180,9 @@ func (c *Client) Authenticate(ctx context.Context, username, password string) er
 		return parseAPIError("bad authorization", resp)
 	}
 
-	res := struct {
+	var res struct {
 		AccessToken string `json:"access_token"`
-	}{}
+	}
 	err = json.NewDecoder(resp.Body).Decode(&res)
 	if err != nil {
 		return err