diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
 # Change Log
 
+## [0.3.0](https://github.com/brendanhay/gogol/tree/0.3.0)
+
+Released: **12 July, 2017**, Compare: [0.3.0](https://github.com/brendanhay/gogol/compare/0.2.0...0.3.0)
+
+### Added
+
+- Addition of offline mode and save + refresh for credentials. [\#66](https://github.com/brendanhay/gogol/pull/66)
+
+### Fixed
+
+- Corrected `Duration` type serialisation. [\#68](https://github.com/brendanhay/gogol/pull/68)
+
+
 ## [0.2.0](https://github.com/brendanhay/gogol/tree/0.2.0)
 
 Released: **12 February, 2017**, Compare: [0.1.1](https://github.com/brendanhay/gogol/compare/0.1.1...0.2.0)
diff --git a/gogol.cabal b/gogol.cabal
--- a/gogol.cabal
+++ b/gogol.cabal
@@ -1,5 +1,5 @@
 name:                  gogol
-version:               0.2.0
+version:               0.3.0
 synopsis:              Comprehensive Google Services SDK.
 homepage:              https://github.com/brendanhay/gogol
 bug-reports:           https://github.com/brendanhay/gogol/issues
@@ -66,7 +66,7 @@
         , directory            >= 1.2
         , exceptions           >= 0.6
         , filepath             >= 1.2
-        , gogol-core           == 0.2.0.*
+        , gogol-core           == 0.3.0.*
         , http-client          >= 0.5 && < 1
         , http-conduit         >= 2.2 && < 3
         , http-media           >= 0.6
diff --git a/src/Network/Google/Auth.hs b/src/Network/Google/Auth.hs
--- a/src/Network/Google/Auth.hs
+++ b/src/Network/Google/Auth.hs
@@ -22,6 +22,8 @@
     , getApplicationDefault
     , fromWellKnownPath
     , fromFilePath
+    , saveAuthorizedUserToWellKnownPath
+    , saveAuthorizedUser
 
     -- ** Installed Application Credentials
     , installedApplication
@@ -33,8 +35,10 @@
     -- ** Thread-safe Storage
     , Store
     , initStore
+    , retrieveAuthFromStore
 
     , Auth           (..)
+    , authToAuthorizedUser
     , exchange
     , refresh
 
@@ -81,6 +85,19 @@
 import qualified Network.HTTP.Conduit                     as Client
 import           Network.HTTP.Types                       (hAuthorization)
 
+-- | 'authToAuthorizedUser' converts 'Auth' into an 'AuthorizedUser'
+--  by returning 'Right' if there is a 'FromClient'-constructed
+--  Credentials and a refreshed token; otherwise, returning
+--  'Left' with error message.
+authToAuthorizedUser :: AllowScopes s => Auth s -> Either Text AuthorizedUser
+authToAuthorizedUser a = AuthorizedUser
+                   <$>  (_clientId <$> getClient)
+                   <*>  maybe (Left "no refresh token") Right (_tokenRefresh (_token a))
+                   <*>  (_clientSecret <$> getClient)
+                        where getClient = case _credentials a of
+                                            FromClient c _ -> Right c
+                                            _ -> Left "not FromClient"
+
 -- | An 'OAuthToken' that can potentially be expired, with the original
 -- credentials that can be used to perform a refresh.
 data Auth (s :: [Symbol]) = Auth
@@ -109,6 +126,12 @@
           -> Manager
           -> m (Store s)
 initStore c l m = exchange c l m >>= fmap Store . liftIO . newMVar
+
+-- | Retrieve auth from storage
+retrieveAuthFromStore :: (MonadIO m, MonadCatch m, AllowScopes s)
+             => Store s
+             -> m (Auth s)
+retrieveAuthFromStore (Store s) = liftIO (readMVar s)
 
 -- | Concurrently read the current token, and if expired, then
 -- safely perform a single serial refresh.
diff --git a/src/Network/Google/Auth/ApplicationDefault.hs b/src/Network/Google/Auth/ApplicationDefault.hs
--- a/src/Network/Google/Auth/ApplicationDefault.hs
+++ b/src/Network/Google/Auth/ApplicationDefault.hs
@@ -20,20 +20,21 @@
 
 import           Control.Applicative
 import           Control.Exception.Lens          (catching, throwingM)
-import           Control.Monad                   (unless)
+import           Control.Monad                   (unless, when)
 import           Control.Monad.Catch
 import           Control.Monad.IO.Class          (MonadIO (..))
 import           Data.Aeson
 import           Data.Aeson.Types                (parseEither)
 import qualified Data.ByteString.Lazy            as LBS
+import           Data.Maybe                      (maybe)
 import qualified Data.Text                       as Text
 import           Network.Google.Compute.Metadata (isGCE)
 import           Network.Google.Internal.Auth
 
 import Network.HTTP.Conduit (Manager)
-import System.Directory     (doesFileExist, getHomeDirectory)
+import System.Directory     (doesFileExist, getHomeDirectory, createDirectoryIfMissing)
 import System.Environment   (lookupEnv)
-import System.FilePath      ((</>))
+import System.FilePath      ((</>), takeDirectory)
 import System.Info          (os)
 
 -- | The environment variable name which is used to specify the directory
@@ -125,6 +126,30 @@
     bs <- liftIO (LBS.readFile f)
     either (throwM . InvalidFileError f . Text.pack) pure
            (fromJSONCredentials bs)
+
+-- | Save 'AuthorizedUser'
+-- /See:/ 'cloudSDKConfigPath', 'defaultCredentialsPath'.
+saveAuthorizedUserToWellKnownPath :: (MonadIO m, MonadCatch m)
+                                  => Bool            -- ^ Force to save if True
+                                  -> AuthorizedUser
+                                  -> m ()
+saveAuthorizedUserToWellKnownPath b a = do
+    d <- defaultCredentialsPath
+    f <- maybe cloudSDKConfigPath pure d
+    liftIO $ createDirectoryIfMissing True $ takeDirectory f
+    saveAuthorizedUser f b a
+
+-- | Save 'AuthorizedUser'
+saveAuthorizedUser :: (MonadIO m, MonadCatch m)
+                   => FilePath
+                   -> Bool            -- ^ Force to save if True
+                   -> AuthorizedUser
+                   -> m ()
+saveAuthorizedUser f b a = do
+    p  <- liftIO (doesFileExist f)
+    when (p && not b) $
+        throwM (FileExistError f)
+    liftIO (LBS.writeFile f $ encode a)
 
 -- | Attempt to parse either a @service_account@ or @authorized_user@ formatted
 -- JSON value to obtain credentials.
diff --git a/src/Network/Google/Auth/InstalledApplication.hs b/src/Network/Google/Auth/InstalledApplication.hs
--- a/src/Network/Google/Auth/InstalledApplication.hs
+++ b/src/Network/Google/Auth/InstalledApplication.hs
@@ -22,9 +22,12 @@
     ( installedApplication
 
     -- * Forming the URL
+    , AccessType (..)
     , redirectURI
     , formURL
+    , formAccessTypeURL
     , formURLWith
+    , formAccessTypeURLWith
 
     -- * Internal Exchange and Refresh
     , exchangeCode
@@ -33,6 +36,7 @@
 
 import           Control.Monad.Catch            (MonadCatch)
 import           Control.Monad.IO.Class         (MonadIO)
+import qualified Data.Text                      as Text
 import qualified Data.Text.Encoding             as Text
 import           GHC.TypeLits                   (Symbol)
 import           Network.Google.Auth.Scope      (AllowScopes (..),
@@ -77,6 +81,9 @@
 installedApplication :: OAuthClient -> OAuthCode s -> Credentials s
 installedApplication = FromClient
 
+-- /See:/ <https://developers.google.com/identity/protocols/OAuth2WebServer#offline>
+data AccessType = Online | Offline deriving (Show, Eq)
+
 -- | The redirection URI used in 'formURL': @urn:ietf:wg:oauth:2.0:oob@.
 redirectURI :: Text
 redirectURI = "urn:ietf:wg:oauth:2.0:oob"
@@ -88,6 +95,12 @@
 formURL :: AllowScopes (s :: [Symbol]) => OAuthClient -> proxy s -> Text
 formURL c = formURLWith c . allowScopes
 
+-- | 'formURL' for 'AccessType'
+--
+-- /See:/ 'formUrl'.
+formAccessTypeURL :: AllowScopes (s :: [Symbol]) => OAuthClient -> AccessType -> proxy s -> Text
+formAccessTypeURL c a = formAccessTypeURLWith c a . allowScopes
+
 -- | Form a URL using 'OAuthScope' values.
 --
 -- /See:/ 'formURL'.
@@ -97,6 +110,13 @@
     <> "&client_id="    <> toQueryParam (_clientId c)
     <> "&redirect_uri=" <> redirectURI
     <> "&scope="        <> Text.decodeUtf8 (queryEncodeScopes ss)
+
+-- | 'formURLWith' for 'AccessType'
+--
+-- /See:/ 'formURLWith'.
+formAccessTypeURLWith :: OAuthClient -> AccessType -> [OAuthScope] -> Text
+formAccessTypeURLWith c a ss = formURLWith c ss
+    <> "&access_type="  <> (Text.toLower . Text.pack $ show a)
 
 -- | Exchange 'OAuthClient' details and the received 'OAuthCode' for a new
 -- 'OAuthToken'.
diff --git a/src/Network/Google/Internal/Auth.hs b/src/Network/Google/Internal/Auth.hs
--- a/src/Network/Google/Internal/Auth.hs
+++ b/src/Network/Google/Internal/Auth.hs
@@ -129,6 +129,13 @@
     , _userSecret  :: !Secret
     } deriving (Eq, Show)
 
+instance ToJSON AuthorizedUser where
+    toJSON (AuthorizedUser i r s) =
+        object [ "client_id"     .= i
+               , "refresh_token" .= r
+               , "client_secret" .= s
+               ]
+
 instance FromJSON AuthorizedUser where
     parseJSON = withObject "authorized_user" $ \o -> AuthorizedUser
         <$> o .: "client_id"
@@ -175,12 +182,13 @@
     toQueryParam (OAuthCode c) = c
     toHeader     (OAuthCode c) = Text.encodeUtf8 c
 
--- | An error thrown when attempting to read AuthN/AuthZ information.
+-- | An error thrown when attempting to read/write AuthN/AuthZ information.
 data AuthError
     = RetrievalError    HttpException
     | MissingFileError  FilePath
     | InvalidFileError  FilePath Text
     | TokenRefreshError Status Text (Maybe Text)
+    | FileExistError    FilePath
       deriving (Show, Typeable)
 
 instance Exception AuthError
