diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
-[![Build Status](https://secure.travis-ci.org/freizl/hoauth2.svg?branch=master)](http://travis-ci.org/freizl/hoauth2)
+[![Build](https://github.com/freizl/hoauth2/actions/workflows/build.yml/badge.svg)](https://github.com/freizl/hoauth2/actions/workflows/build.yml)
+[![lint](https://github.com/freizl/hoauth2/actions/workflows/lint.yml/badge.svg)](https://github.com/freizl/hoauth2/actions/workflows/lint.yml)
+[![Travis Status](https://secure.travis-ci.org/freizl/hoauth2.svg?branch=master)](http://travis-ci.org/freizl/hoauth2)
 [![Hackage](https://img.shields.io/hackage/v/hoauth2.svg)](https://hackage.haskell.org/package/hoauth2)
 
 # Introduction
diff --git a/example/IDP.hs b/example/IDP.hs
--- a/example/IDP.hs
+++ b/example/IDP.hs
@@ -13,6 +13,7 @@
 import qualified IDP.Okta as IOkta
 import qualified IDP.StackExchange as IStackExchange
 import qualified IDP.Weibo as IWeibo
+import qualified IDP.Slack as ISlack
 import qualified IDP.ZOHO as IZOHO
 import Session
 import Types
@@ -32,6 +33,7 @@
     IDPApp IStackExchange.StackExchange,
     IDPApp IWeibo.Weibo,
     IDPApp IAuth0.Auth0,
+    IDPApp ISlack.Slack,
     IDPApp IZOHO.ZOHO
   ]
 
diff --git a/example/IDP/Okta.hs b/example/IDP/Okta.hs
--- a/example/IDP/Okta.hs
+++ b/example/IDP/Okta.hs
@@ -1,20 +1,21 @@
-{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE QuasiQuotes       #-}
+{-# LANGUAGE QuasiQuotes #-}
 
 module IDP.Okta where
-import           Data.Aeson
-import           Data.Bifunctor
-import           Data.Hashable
-import           Data.Text.Lazy                 ( Text )
-import           GHC.Generics
-import           Keys
-import           Network.OAuth.OAuth2
-import           Types
-import           URI.ByteString
-import           URI.ByteString.QQ
-import           Utils
 
+import Data.Aeson
+import Data.Bifunctor
+import Data.Hashable
+import Data.Text.Lazy (Text)
+import GHC.Generics
+import Keys
+import Network.OAuth.OAuth2
+import Types
+import URI.ByteString
+import URI.ByteString.QQ
+import Utils
+
 data Okta = Okta
   deriving (Show, Generic)
 
@@ -35,28 +36,36 @@
     re <- authGetJSON mgr at userInfoUri
     return (second toLoginUser re)
 
+-- | https://developer.okta.com/docs/reference/api/oidc/#request-parameters
+-- Okta Org AS doesn't support consent
+-- Okta Custom AS does support consent via config (what scope shall prompt consent)
+--
 instance HasAuthUri Okta where
-  authUri _ = createCodeUri
-    oktaKey
-    [ ("state", "Okta.test-state-123")
-    , ( "scope"
-      , "openid profile offline_access okta.users.read.self okta.users.read"
-      )
-    ]
+  authUri _ =
+    createCodeUri
+      oktaKey
+      [ ("state", "Okta.test-state-123"),
+        ( "scope",
+          "openid profile"
+          -- , "openid profile offline_access okta.users.read.self okta.users.read"
+        ),
+        ("prompt", "login consent")
+      ]
 
 data OktaUser = OktaUser
-  { name              :: Text
-  , preferredUsername :: Text
+  { name :: Text,
+    preferredUsername :: Text
   }
   deriving (Show, Generic)
 
 instance FromJSON OktaUser where
   parseJSON =
-    genericParseJSON defaultOptions { fieldLabelModifier = camelTo2 '_' }
+    genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}
 
 userInfoUri :: URI
-userInfoUri = [uri|http://rain.okta1.com:1802/oauth2/v1/userinfo|]
+userInfoUri = [uri|https://hw2.trexcloud.com/oauth2/v1/userinfo|]
 
-toLoginUser :: OktaUser -> LoginUser
-toLoginUser ouser = LoginUser { loginUserName = name ouser }
+-- userInfoUri = [uri|https://dev-148986.oktapreview.com/oauth2/v1/userinfo|]
 
+toLoginUser :: OktaUser -> LoginUser
+toLoginUser ouser = LoginUser {loginUserName = name ouser}
diff --git a/example/IDP/Slack.hs b/example/IDP/Slack.hs
new file mode 100644
--- /dev/null
+++ b/example/IDP/Slack.hs
@@ -0,0 +1,63 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+
+module IDP.Slack where
+
+import Data.Aeson
+import Data.Bifunctor
+import Data.Hashable
+import Data.Text.Lazy (Text)
+import GHC.Generics
+import Keys
+import Network.OAuth.OAuth2
+import Types
+import URI.ByteString
+import URI.ByteString.QQ
+import Utils
+
+data Slack = Slack
+  deriving (Show, Generic)
+
+instance Hashable Slack
+
+instance IDP Slack
+
+instance HasLabel Slack
+
+instance HasTokenReq Slack where
+  tokenReq _ mgr = fetchAccessToken mgr slackKey
+
+instance HasTokenRefreshReq Slack where
+  tokenRefreshReq _ mgr = refreshAccessToken mgr slackKey
+
+instance HasUserReq Slack where
+  userReq _ mgr at = do
+    re <- authGetJSON mgr at userInfoUri
+    return (second toLoginUser re)
+
+instance HasAuthUri Slack where
+  authUri _ =
+    createCodeUri
+      slackKey
+      [ ("state", "Slack.test-state-123"),
+        ( "scope",
+          "openid profile email"
+        )
+      ]
+
+data SlackUser = SlackUser
+  { name :: Text,
+    email :: Text
+  }
+  deriving (Show, Generic)
+
+instance FromJSON SlackUser where
+  parseJSON =
+    genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}
+
+userInfoUri :: URI
+userInfoUri = [uri|https://slack.com/api/openid.connect.userInfo|]
+
+toLoginUser :: SlackUser -> LoginUser
+toLoginUser ouser = LoginUser {loginUserName = name ouser}
diff --git a/example/Keys.hs b/example/Keys.hs
--- a/example/Keys.hs
+++ b/example/Keys.hs
@@ -9,60 +9,60 @@
 
 weiboKey :: OAuth2
 weiboKey = OAuth2
-  { oauthClientId            = "xxxxxxxxxxxxxxx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/oauthCallback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://api.weibo.com/oauth2/authorize|]
-  , oauthAccessTokenEndpoint = [uri|https://api.weibo.com/oauth2/access_token|]
+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/oauthCallback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://api.weibo.com/oauth2/authorize|]
+  , oauth2TokenEndpoint = [uri|https://api.weibo.com/oauth2/access_token|]
   }
 
 -- | http://developer.github.com/v3/oauth/
 githubKey :: OAuth2
 githubKey = OAuth2
-  { oauthClientId            = "xxxxxxxxxxxxxxx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/githubCallback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://github.com/login/oauth/authorize|]
-  , oauthAccessTokenEndpoint =
+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/githubCallback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://github.com/login/oauth/authorize|]
+  , oauth2TokenEndpoint =
     [uri|https://github.com/login/oauth/access_token|]
   }
 
 -- | oauthCallback = Just "https://developers.google.com/oauthplayground"
 googleKey :: OAuth2
 googleKey = OAuth2
-  { oauthClientId            = "xxxxxxxxxxxxxxx.apps.googleusercontent.com"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/googleCallback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://accounts.google.com/o/oauth2/auth|]
-  , oauthAccessTokenEndpoint = [uri|https://www.googleapis.com/oauth2/v3/token|]
+  { oauth2ClientId            = "xxxxxxxxxxxxxxx.apps.googleusercontent.com"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/googleCallback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://accounts.google.com/o/oauth2/auth|]
+  , oauth2TokenEndpoint = [uri|https://www.googleapis.com/oauth2/v3/token|]
   }
 
 facebookKey :: OAuth2
 facebookKey = OAuth2
-  { oauthClientId            = "xxxxxxxxxxxxxxx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://t.haskellcn.org/cb|]
-  , oauthOAuthorizeEndpoint  = [uri|https://www.facebook.com/dialog/oauth|]
-  , oauthAccessTokenEndpoint =
+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://t.haskellcn.org/cb|]
+  , oauth2AuthorizeEndpoint  = [uri|https://www.facebook.com/dialog/oauth|]
+  , oauth2TokenEndpoint =
     [uri|https://graph.facebook.com/v2.3/oauth/access_token|]
   }
 
 doubanKey :: OAuth2
 doubanKey = OAuth2
-  { oauthClientId            = "xxxxxxxxxxxxxxx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://localhost:9999/oauthCallback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://www.douban.com/service/auth2/auth|]
-  , oauthAccessTokenEndpoint = [uri|https://www.douban.com/service/auth2/token|]
+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9999/oauthCallback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://www.douban.com/service/auth2/auth|]
+  , oauth2TokenEndpoint = [uri|https://www.douban.com/service/auth2/token|]
   }
 
 fitbitKey :: OAuth2
 fitbitKey = OAuth2
-  { oauthClientId            = "xxxxxx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://www.fitbit.com/oauth2/authorize|]
-  , oauthAccessTokenEndpoint = [uri|https://api.fitbit.com/oauth2/token|]
+  { oauth2ClientId            = "xxxxxx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://www.fitbit.com/oauth2/authorize|]
+  , oauth2TokenEndpoint = [uri|https://api.fitbit.com/oauth2/token|]
   }
 
 -- fix key from your application edit page
@@ -72,58 +72,70 @@
 
 stackexchangeKey :: OAuth2
 stackexchangeKey = OAuth2
-  { oauthClientId            = "xx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://c.haskellcn.org/cb|]
-  , oauthOAuthorizeEndpoint  = [uri|https://stackexchange.com/oauth|]
-  , oauthAccessTokenEndpoint =
+  { oauth2ClientId            = "xx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://c.haskellcn.org/cb|]
+  , oauth2AuthorizeEndpoint  = [uri|https://stackexchange.com/oauth|]
+  , oauth2TokenEndpoint =
     [uri|https://stackexchange.com/oauth/access_token|]
   }
 dropboxKey :: OAuth2
 dropboxKey = OAuth2
-  { oauthClientId            = "xxx"
-  , oauthClientSecret        = Just "xxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://www.dropbox.com/1/oauth2/authorize|]
-  , oauthAccessTokenEndpoint = [uri|https://api.dropboxapi.com/oauth2/token|]
+  { oauth2ClientId            = "xxx"
+  , oauth2ClientSecret        = Just "xxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://www.dropbox.com/1/oauth2/authorize|]
+  , oauth2TokenEndpoint = [uri|https://api.dropboxapi.com/oauth2/token|]
   }
 
 oktaKey :: OAuth2
 oktaKey = OAuth2
-  { oauthClientId            = "xxx"
-  , oauthClientSecret        = Just "xxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  =
+  { oauth2ClientId            = "xxx"
+  , oauth2ClientSecret        = Just "xxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  =
     [uri|https://dev-148986.oktapreview.com/oauth2/v1/authorize|]
-  , oauthAccessTokenEndpoint =
+  , oauth2TokenEndpoint =
     [uri|https://dev-148986.oktapreview.com/oauth2/v1/token|]
   }
 
 azureADKey :: OAuth2
 azureADKey = OAuth2
-  { oauthClientId            = "xxx"
-  , oauthClientSecret        = Just "xxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  =
+  { oauth2ClientId            = "xxx"
+  , oauth2ClientSecret        = Just "xxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  =
     [uri|https://login.windows.net/common/oauth2/authorize|]
-  , oauthAccessTokenEndpoint =
+  , oauth2TokenEndpoint =
     [uri|https://login.windows.net/common/oauth2/token|]
   }
 
 zohoKey :: OAuth2
 zohoKey = OAuth2
-  { oauthClientId            = "xxx"
-  , oauthClientSecret        = Just "xxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://accounts.zoho.com/oauth/v2/auth|]
-  , oauthAccessTokenEndpoint = [uri|https://accounts.zoho.com/oauth/v2/token|]
+  { oauth2ClientId            = "xxx"
+  , oauth2ClientSecret        = Just "xxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://accounts.zoho.com/oauth/v2/auth|]
+  , oauth2TokenEndpoint = [uri|https://accounts.zoho.com/oauth/v2/token|]
   }
 
 auth0Key :: OAuth2
 auth0Key = OAuth2
-  { oauthClientId            = "xxx"
-  , oauthClientSecret        = Just "xxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://foo.auth0.com/authorize|]
-  , oauthAccessTokenEndpoint = [uri|https://foo.auth0.com/oauth/token|]
+  { oauth2ClientId            = "xxx"
+  , oauth2ClientSecret        = Just "xxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://foo.auth0.com/authorize|]
+  , oauth2TokenEndpoint = [uri|https://foo.auth0.com/oauth/token|]
   }
+
+-- https://api.slack.com/authentication/sign-in-with-slack
+-- https://slack.com/.well-known/openid-configuration
+slackKey :: OAuth2
+slackKey =
+  OAuth2
+    { oauth2ClientId = ""
+    , oauth2ClientSecret = Just ""
+    , oauth2RedirectUri = Just [uri|http://localhost:9988/oauth2/callback|]
+    , oauth2AuthorizeEndpoint = [uri|https://slack.com/openid/connect/authorize|]
+    , oauth2TokenEndpoint = [uri|https://slack.com/api/openid.connect.token|]
+    }
diff --git a/example/Keys.hs.sample b/example/Keys.hs.sample
--- a/example/Keys.hs.sample
+++ b/example/Keys.hs.sample
@@ -9,60 +9,60 @@
 
 weiboKey :: OAuth2
 weiboKey = OAuth2
-  { oauthClientId            = "xxxxxxxxxxxxxxx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/oauthCallback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://api.weibo.com/oauth2/authorize|]
-  , oauthAccessTokenEndpoint = [uri|https://api.weibo.com/oauth2/access_token|]
+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/oauthCallback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://api.weibo.com/oauth2/authorize|]
+  , oauth2TokenEndpoint = [uri|https://api.weibo.com/oauth2/access_token|]
   }
 
 -- | http://developer.github.com/v3/oauth/
 githubKey :: OAuth2
 githubKey = OAuth2
-  { oauthClientId            = "xxxxxxxxxxxxxxx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/githubCallback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://github.com/login/oauth/authorize|]
-  , oauthAccessTokenEndpoint =
+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/githubCallback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://github.com/login/oauth/authorize|]
+  , oauth2TokenEndpoint =
     [uri|https://github.com/login/oauth/access_token|]
   }
 
 -- | oauthCallback = Just "https://developers.google.com/oauthplayground"
 googleKey :: OAuth2
 googleKey = OAuth2
-  { oauthClientId            = "xxxxxxxxxxxxxxx.apps.googleusercontent.com"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://127.0.0.1:9988/googleCallback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://accounts.google.com/o/oauth2/auth|]
-  , oauthAccessTokenEndpoint = [uri|https://www.googleapis.com/oauth2/v3/token|]
+  { oauth2ClientId            = "xxxxxxxxxxxxxxx.apps.googleusercontent.com"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://127.0.0.1:9988/googleCallback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://accounts.google.com/o/oauth2/auth|]
+  , oauth2TokenEndpoint = [uri|https://www.googleapis.com/oauth2/v3/token|]
   }
 
 facebookKey :: OAuth2
 facebookKey = OAuth2
-  { oauthClientId            = "xxxxxxxxxxxxxxx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://t.haskellcn.org/cb|]
-  , oauthOAuthorizeEndpoint  = [uri|https://www.facebook.com/dialog/oauth|]
-  , oauthAccessTokenEndpoint =
+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://t.haskellcn.org/cb|]
+  , oauth2AuthorizeEndpoint  = [uri|https://www.facebook.com/dialog/oauth|]
+  , oauth2TokenEndpoint =
     [uri|https://graph.facebook.com/v2.3/oauth/access_token|]
   }
 
 doubanKey :: OAuth2
 doubanKey = OAuth2
-  { oauthClientId            = "xxxxxxxxxxxxxxx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://localhost:9999/oauthCallback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://www.douban.com/service/auth2/auth|]
-  , oauthAccessTokenEndpoint = [uri|https://www.douban.com/service/auth2/token|]
+  { oauth2ClientId            = "xxxxxxxxxxxxxxx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9999/oauthCallback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://www.douban.com/service/auth2/auth|]
+  , oauth2TokenEndpoint = [uri|https://www.douban.com/service/auth2/token|]
   }
 
 fitbitKey :: OAuth2
 fitbitKey = OAuth2
-  { oauthClientId            = "xxxxxx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://www.fitbit.com/oauth2/authorize|]
-  , oauthAccessTokenEndpoint = [uri|https://api.fitbit.com/oauth2/token|]
+  { oauth2ClientId            = "xxxxxx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://www.fitbit.com/oauth2/authorize|]
+  , oauth2TokenEndpoint = [uri|https://api.fitbit.com/oauth2/token|]
   }
 
 -- fix key from your application edit page
@@ -72,58 +72,70 @@
 
 stackexchangeKey :: OAuth2
 stackexchangeKey = OAuth2
-  { oauthClientId            = "xx"
-  , oauthClientSecret        = Just "xxxxxxxxxxxxxxx"
-  , oauthCallback            = Just [uri|http://c.haskellcn.org/cb|]
-  , oauthOAuthorizeEndpoint  = [uri|https://stackexchange.com/oauth|]
-  , oauthAccessTokenEndpoint =
+  { oauth2ClientId            = "xx"
+  , oauth2ClientSecret        = Just "xxxxxxxxxxxxxxx"
+  , oauth2RedirectUri            = Just [uri|http://c.haskellcn.org/cb|]
+  , oauth2AuthorizeEndpoint  = [uri|https://stackexchange.com/oauth|]
+  , oauth2TokenEndpoint =
     [uri|https://stackexchange.com/oauth/access_token|]
   }
 dropboxKey :: OAuth2
 dropboxKey = OAuth2
-  { oauthClientId            = "xxx"
-  , oauthClientSecret        = Just "xxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://www.dropbox.com/1/oauth2/authorize|]
-  , oauthAccessTokenEndpoint = [uri|https://api.dropboxapi.com/oauth2/token|]
+  { oauth2ClientId            = "xxx"
+  , oauth2ClientSecret        = Just "xxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://www.dropbox.com/1/oauth2/authorize|]
+  , oauth2TokenEndpoint = [uri|https://api.dropboxapi.com/oauth2/token|]
   }
 
 oktaKey :: OAuth2
 oktaKey = OAuth2
-  { oauthClientId            = "xxx"
-  , oauthClientSecret        = Just "xxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  =
+  { oauth2ClientId            = "xxx"
+  , oauth2ClientSecret        = Just "xxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  =
     [uri|https://dev-148986.oktapreview.com/oauth2/v1/authorize|]
-  , oauthAccessTokenEndpoint =
+  , oauth2TokenEndpoint =
     [uri|https://dev-148986.oktapreview.com/oauth2/v1/token|]
   }
 
 azureADKey :: OAuth2
 azureADKey = OAuth2
-  { oauthClientId            = "xxx"
-  , oauthClientSecret        = Just "xxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  =
+  { oauth2ClientId            = "xxx"
+  , oauth2ClientSecret        = Just "xxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  =
     [uri|https://login.windows.net/common/oauth2/authorize|]
-  , oauthAccessTokenEndpoint =
+  , oauth2TokenEndpoint =
     [uri|https://login.windows.net/common/oauth2/token|]
   }
 
 zohoKey :: OAuth2
 zohoKey = OAuth2
-  { oauthClientId            = "xxx"
-  , oauthClientSecret        = Just "xxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://accounts.zoho.com/oauth/v2/auth|]
-  , oauthAccessTokenEndpoint = [uri|https://accounts.zoho.com/oauth/v2/token|]
+  { oauth2ClientId            = "xxx"
+  , oauth2ClientSecret        = Just "xxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://accounts.zoho.com/oauth/v2/auth|]
+  , oauth2TokenEndpoint = [uri|https://accounts.zoho.com/oauth/v2/token|]
   }
 
 auth0Key :: OAuth2
 auth0Key = OAuth2
-  { oauthClientId            = "xxx"
-  , oauthClientSecret        = Just "xxx"
-  , oauthCallback            = Just [uri|http://localhost:9988/oauth2/callback|]
-  , oauthOAuthorizeEndpoint  = [uri|https://foo.auth0.com/authorize|]
-  , oauthAccessTokenEndpoint = [uri|https://foo.auth0.com/oauth/token|]
+  { oauth2ClientId            = "xxx"
+  , oauth2ClientSecret        = Just "xxx"
+  , oauth2RedirectUri            = Just [uri|http://localhost:9988/oauth2/callback|]
+  , oauth2AuthorizeEndpoint  = [uri|https://foo.auth0.com/authorize|]
+  , oauth2TokenEndpoint = [uri|https://foo.auth0.com/oauth/token|]
   }
+
+-- https://api.slack.com/authentication/sign-in-with-slack
+-- https://slack.com/.well-known/openid-configuration
+slackKey :: OAuth2
+slackKey =
+  OAuth2
+    { oauth2ClientId = ""
+    , oauth2ClientSecret = Just ""
+    , oauth2RedirectUri = Just [uri|http://localhost:9988/oauth2/callback|]
+    , oauth2AuthorizeEndpoint = [uri|https://slack.com/openid/connect/authorize|]
+    , oauth2TokenEndpoint = [uri|https://slack.com/api/openid.connect.token|]
+    }
diff --git a/example/README.org b/example/README.org
--- a/example/README.org
+++ b/example/README.org
@@ -18,6 +18,9 @@
 
   - <https://developer.linkedin.com>
 
+* TODO
+- [ ] Split ~Key.hs~ to each IDP and read ~.env~ file if exists for override
+
 * NOTES
 - classes in Types.hs takes a (`IDP`) as first parameter but it is actually not used. bad pattern. how to fix it??
 - refactor: `App.hs` is messy!
diff --git a/example/templates/index.mustache b/example/templates/index.mustache
--- a/example/templates/index.mustache
+++ b/example/templates/index.mustache
@@ -8,7 +8,7 @@
         <section class="login-with">
 
             {{^isLogin}}
-            <a href="{{codeFlowUri}}">Login {{name}}</a>
+            <a href="{{codeFlowUri}}">Sign in with {{name}}</a>
             {{/isLogin}}
 
             {{#isLogin}}
@@ -25,6 +25,7 @@
         <h2>Notes</h2>
         <ol>
             <li>for StackExchange, the callback domain is localhost, have manually add port 9988.</li>
+            <li>for Slack, the callback url has to be https and without any port hence need manual intervention. (TODO: add tls to the server)</li>
         </ol>
         </p>
     </body>
diff --git a/hoauth2.cabal b/hoauth2.cabal
--- a/hoauth2.cabal
+++ b/hoauth2.cabal
@@ -1,7 +1,7 @@
 Cabal-version: 2.4
 Name:                hoauth2
 -- http://wiki.haskell.org/Package_versioning_policy
-Version:             1.16.2
+Version:             2.0.0
 
 Synopsis:            Haskell OAuth2 authentication client
 
@@ -46,6 +46,7 @@
                     example/IDP/Fitbit.hs
                     example/IDP/Douban.hs
                     example/IDP/Linkedin.hs
+                    example/IDP/Slack.hs
                     example/IDP/Auth0.hs
                     example/IDP.hs
                     example/App.hs
@@ -86,6 +87,7 @@
                  uri-bytestring       >= 0.2.3.1 && < 0.4,
                  uri-bytestring-aeson >= 0.1   && < 0.2,
                  microlens            >= 0.4.0 && < 0.5,
+                 hashable >= 1.2.6 && < 1.5.0,
                  exceptions           >= 0.8.3 && < 0.11
 
   ghc-options: -Wall -fwarn-tabs -funbox-strict-fields
@@ -107,6 +109,7 @@
                        IDP.Fitbit
                        IDP.Github
                        IDP.Google
+                       IDP.Slack
                        IDP.Auth0
                        IDP.Okta
                        IDP.StackExchange
@@ -132,7 +135,7 @@
                        microlens            >= 0.4.0 && < 0.5,
                        unordered-containers >= 0.2.5,
                        wai-middleware-static >= 0.8.1 && < 0.10.0,
-                       mustache >= 2.2.3 && < 2.5.0,
+                       mustache >= 2.2.3 && < 2.4.0,
                        scotty >= 0.10.0 && < 0.13,
                        binary >= 0.8.3.0 && < 0.8.9,
                        parsec >= 3.1.11 && < 3.2.0 ,
diff --git a/src/Network/OAuth/OAuth2/HttpClient.hs b/src/Network/OAuth/OAuth2/HttpClient.hs
--- a/src/Network/OAuth/OAuth2/HttpClient.hs
+++ b/src/Network/OAuth/OAuth2/HttpClient.hs
@@ -65,7 +65,7 @@
 fetchAccessToken2 mgr oa code = do
   let (url, body1) = accessTokenUrl oa code
   let secret x = [("client_secret", T.encodeUtf8 x)]
-  let extraBody = ("client_id", T.encodeUtf8 $ oauthClientId oa) : maybe [] secret (oauthClientSecret oa)
+  let extraBody = ("client_id", T.encodeUtf8 $ oauth2ClientId oa) : maybe [] secret (oauth2ClientSecret oa)
   doJSONPostRequest mgr oa url (extraBody ++ body1)
 
 -- | Fetch a new AccessToken with the Refresh Token with authentication in request header.
@@ -92,7 +92,7 @@
 refreshAccessToken2 manager oa token = do
   let (uri, body) = refreshAccessTokenUrl oa token
   let secret x = [("client_secret", T.encodeUtf8 x)]
-  let extraBody = ("client_id", T.encodeUtf8 $ oauthClientId oa) : maybe [] secret (oauthClientSecret oa)
+  let extraBody = ("client_id", T.encodeUtf8 $ oauth2ClientId oa) : maybe [] secret (oauth2ClientSecret oa)
   doJSONPostRequest manager oa uri (extraBody ++ body)
 
 -- | Conduct post request and return response as JSON.
@@ -113,8 +113,8 @@
 doSimplePostRequest manager oa url body = fmap handleOAuth2TokenResponse go
                                   where go = do
                                              req <- uriToRequest url
-                                             let addBasicAuth = case oauthClientSecret oa of
-                                                   (Just secret) -> applyBasicAuth (T.encodeUtf8 $ oauthClientId oa) (T.encodeUtf8 secret)
+                                             let addBasicAuth = case oauth2ClientSecret oa of
+                                                   (Just secret) -> applyBasicAuth (T.encodeUtf8 $ oauth2ClientId oa) (T.encodeUtf8 secret)
                                                    Nothing -> id
                                                  req' = (addBasicAuth . updateRequestHeaders Nothing) req
                                              httpLbs (urlEncodedBody body req') manager
diff --git a/src/Network/OAuth/OAuth2/Internal.hs b/src/Network/OAuth/OAuth2/Internal.hs
--- a/src/Network/OAuth/OAuth2/Internal.hs
+++ b/src/Network/OAuth/OAuth2/Internal.hs
@@ -2,6 +2,7 @@
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TupleSections #-}
+{-# LANGUAGE RecordWildCards #-}
 {-# OPTIONS_HADDOCK -ignore-exports #-}
 
 -- | A simple OAuth2 Haskell binding.  (This is supposed to be
@@ -19,13 +20,14 @@
 import Data.Maybe
 import Data.Text (Text, pack, unpack)
 import Data.Text.Encoding
-import GHC.Generics
 import Lens.Micro
 import Lens.Micro.Extras
 import Network.HTTP.Conduit as C
 import qualified Network.HTTP.Types as H
 import URI.ByteString
 import URI.ByteString.Aeson ()
+import Data.Hashable
+import GHC.Generics
 
 --------------------------------------------------
 
@@ -34,15 +36,27 @@
 --------------------------------------------------
 
 -- | Query Parameter Representation
+-- TODO: fix typo in OAuthorizeEndpoint
+-- rename AccessToken to TokenEndpoint
+-- rename callback to redirectUri
+
 data OAuth2 = OAuth2
-  { oauthClientId :: Text,
-    oauthClientSecret :: Maybe Text,
-    oauthOAuthorizeEndpoint :: URI,
-    oauthAccessTokenEndpoint :: URI,
-    oauthCallback :: Maybe URI
+  { oauth2ClientId :: Text,
+    oauth2ClientSecret :: Maybe Text,
+    oauth2AuthorizeEndpoint :: URIRef Absolute,
+    oauth2TokenEndpoint :: URIRef Absolute,
+    oauth2RedirectUri :: Maybe ( URIRef Absolute )
   }
   deriving (Show, Eq)
 
+instance Hashable OAuth2 where
+  hashWithSalt salt OAuth2{..} = salt
+    `hashWithSalt` hash oauth2ClientId
+    `hashWithSalt` hash oauth2ClientSecret
+    `hashWithSalt` hash (show oauth2AuthorizeEndpoint)
+    `hashWithSalt` hash (show oauth2TokenEndpoint)
+    `hashWithSalt` hash (show oauth2RedirectUri)
+
 newtype AccessToken = AccessToken {atoken :: Text} deriving (Binary, Eq, Show, FromJSON, ToJSON)
 
 newtype RefreshToken = RefreshToken {rtoken :: Text} deriving (Binary, Eq, Show, FromJSON, ToJSON)
@@ -138,13 +152,13 @@
 -- | Prepare the authorization URL.  Redirect to this URL
 -- asking for user interactive authentication.
 authorizationUrl :: OAuth2 -> URI
-authorizationUrl oa = over (queryL . queryPairsL) (++ queryParts) (oauthOAuthorizeEndpoint oa)
+authorizationUrl oa = over (queryL . queryPairsL) (++ queryParts) (oauth2AuthorizeEndpoint oa)
   where
     queryParts =
       catMaybes
-        [ Just ("client_id", encodeUtf8 $ oauthClientId oa),
+        [ Just ("client_id", encodeUtf8 $ oauth2ClientId oa),
           Just ("response_type", "code"),
-          fmap (("redirect_uri",) . serializeURIRef') (oauthCallback oa)
+          fmap (("redirect_uri",) . serializeURIRef') (oauth2RedirectUri oa)
         ]
 
 -- | Prepare the URL and the request body query for fetching an access token.
@@ -168,11 +182,11 @@
   (URI, PostBody)
 accessTokenUrl' oa code gt = (uri, body)
   where
-    uri = oauthAccessTokenEndpoint oa
+    uri = oauth2TokenEndpoint oa
     body =
       catMaybes
         [ Just ("code", encodeUtf8 $ extoken code),
-          ("redirect_uri",) . serializeURIRef' <$> oauthCallback oa,
+          ("redirect_uri",) . serializeURIRef' <$> oauth2RedirectUri oa,
           fmap (("grant_type",) . encodeUtf8) gt
         ]
 
@@ -186,7 +200,7 @@
   (URI, PostBody)
 refreshAccessTokenUrl oa token = (uri, body)
   where
-    uri = oauthAccessTokenEndpoint oa
+    uri = oauth2TokenEndpoint oa
     body =
       [ ("grant_type", "refresh_token"),
         ("refresh_token", encodeUtf8 $ rtoken token)
