When the state token is sent to an OAuth2 provider, it undergoes %-encoding as a URL parameter. Presumably, the OAuth2 provider decodes it as part of handling things (because it would take work to prevent their own web frameworks from doing so), and then re-%-encodes it coming back to us again as a callback parameter. For us, and all existing providers, + is not a %-encoded character, so it's sent as-is and sent back as-is. So far so good. ClassLink, though, chooses to decode + to space. I'm not aware of the actual spec or if this is a reasonable thing to do, but they do. This results in them sending %20 back to us, which doesn't match and we fail. We can't predict or prescribe what providers do in this area, so our options are: - Look for a match in our Session as-is OR with spaces replaced by + This is harder than it sounds: a token could contain +'s or spaces, and we'd be getting back only spaces. To succeed, we'd actually have to check every permutation of space/+ substitution. - Filter + from our tokens The only downside is we may generate slightly fewer than 30 characters, and so produce slightly less secure tokens. I chose this option. - Generate tokens without + to begin with This would be ideal, but I'm just not familiar enough with Crypto.Random. I would happily accept a PR to use this option. |
||
|---|---|---|
| .circleci | ||
| example | ||
| src | ||
| test | ||
| .env.example | ||
| .gitignore | ||
| .hlint.yaml | ||
| .stylish-haskell.yaml | ||
| CHANGELOG.md | ||
| LICENSE | ||
| Makefile | ||
| package.yaml | ||
| README.md | ||
| Setup.lhs | ||
| stack-lts-13.2.yaml | ||
| stack-lts-13.2.yaml.lock | ||
| stack-lts-16.10.yaml | ||
| stack-lts-16.10.yaml.lock | ||
| stack.yaml | ||
| stack.yaml.lock | ||
Yesod.Auth.OAuth2
OAuth2 AuthPlugins for Yesod.
Usage
import Yesod.Auth
import Yesod.Auth.OAuth2.GitHub
instance YesodAuth App where
-- ...
authPlugins _ = [oauth2GitHub clientId clientSecret]
clientId :: Text
clientId = "..."
clientSecret :: Text
clientSecret = "..."
Some plugins, such as GitHub and Slack, have scoped functions for requesting additional information:
oauth2SlackScoped [SlackBasicScope, SlackEmailScope] clientId clientSecret
Working with Extra Data
We put the minimal amount of user data possible in credsExtra -- just enough
to support you parsing or fetching additional data yourself.
For example, if you work with GitHub and GitHub user profiles, you likely
already have a model and a way to parse the /user response. Rather than
duplicate all that in our library, we try to make it easy for you to re-use that
code yourself:
authenticate creds = do
let
-- You can run your own FromJSON parser on the response we already have
eGitHubUser :: Either String GitHubUser
eGitHubUser = getUserResponseJSON creds
-- Avert your eyes, simplified example
Just accessToken = getAccessToken creds
Right githubUser = eGitHubUser
-- Or make followup requests using our access token
runGitHub accessToken $ userRepositories githubUser
-- Or store it for later
insert User
{ userIdent = credsIdent creds
, userAccessToken = accessToken
}
NOTE: Avoid looking up values in credsExtra yourself; prefer the provided
get functions. The data representation itself is no longer considered public
API.
Local Providers
If we don't supply a "Provider" (e.g. GitHub, Google, etc) you need, you can
write your own using our provided Prelude:
import Yesod.Auth.OAuth2.Prelude
pluginName :: Text
pluginName = "mysite"
oauth2MySite :: YesodAuth m => Text -> Text -> AuthPlugin m
oauth2MySite clientId clientSecret =
authOAuth2 pluginName oauth2 $ \manager token -> do
-- Fetch a profile using the manager and token, leave it a ByteString
userResponse <- -- ...
-- Parse it to your preferred identifier, e.g. with Data.Aeson
userId <- -- ...
-- See authGetProfile for the typical case
pure Creds
{ credsPlugin = pluginName
, credsIdent = userId
, credsExtra = setExtra token userResponse
}
where
oauth2 = OAuth2
{ oauthClientId = clientId
, oauthClientSecret = Just clientSecret
, oauthOAuthorizeEndpoint = "https://mysite.com/oauth/authorize"
, oauthAccessTokenEndpoint = "https://mysite.com/oauth/token"
, oauthCallback = Nothing
}
The Prelude module is considered public API, though we may build something
higher-level that is more convenient for this use-case in the future.
Development & Tests
stack setup
stack build --dependencies-only
stack build --pedantic --test
Please also run HLint and Weeder before submitting PRs.