IHP provides built-in support for OAuth login with third-party identity providers. This allows users to sign in using their existing Google or GitHub accounts, streamlining the registration and login process.
OAuth login functionality requires IHP Pro.
Supported Providers
IHP currently supports OAuth login with:
- Google - Login with Google accounts
- GitHub - Login with GitHub accounts
Google OAuth
Installation
Add the dependency
Open your project’s flake.nix and add ihp-oauth-google to your haskellDeps:let
ihp = ..
haskellEnv = import "${ihp}/NixSupport/default.nix" {
ihp = ihp;
haskellDeps = p: with p; [
# ...
ihp-oauth-google # <----- ADD THIS LINE
];
otherDeps = p: with p; [
];
projectPath = ./.;
};
in
haskellEnv
Install the package
Stop your local development server and run:
Database Schema
Add a google_user_id column to your users table. Open Application/Schema.sql:
CREATE TABLE users (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
-- ... ,
google_user_id TEXT
);
The column must be nullable with NULL as the default value.
Controller Setup
Create a new file Web/Controller/GoogleOAuth.hs:
module Web.Controller.GoogleOAuth where
import Web.Controller.Prelude
import Web.Controller.Sessions ()
import IHP.OAuth.Google.Controller
import qualified IHP.OAuth.Google.Types as Google
instance Controller Google.GoogleOAuthController where
action Google.NewSessionWithGoogleAction = newSessionWithGoogleAction @User
action Google.GoogleConnectCallbackAction = googleConnectCallbackAction @User
instance GoogleOAuthControllerConfig User where
beforeCreateUser user googleClaims = user
|> set #isConfirmed True -- Auto-confirm user's email
Routing
Open Web/Routes.hs and:
-
Import the type definition:
import IHP.OAuth.Google.Types
-
Enable AutoRoute:
instance AutoRoute GoogleOAuthController
FrontController
Open Web/FrontController.hs and:
-
Add imports:
import IHP.OAuth.Google.Types
import Web.Controller.GoogleOAuth
-
Add the controller:
instance FrontController WebApplication where
controllers =
[ startPage StartpageAction
-- ...
, parseRoute @GoogleOAuthController -- <----- ADD THIS
]
View Prelude
Update Web/View/Prelude.hs to include Google OAuth types:
module Web.View.Prelude
( module IHP.ViewPrelude
-- ...
, module IHP.OAuth.Google.Types -- <---- ADD THIS
) where
import IHP.ViewPrelude
-- ...
import IHP.OAuth.Google.Types -- <---- ADD THIS
This allows you to use pathTo NewSessionWithGoogleAction without explicit imports.
Configuration
Get Google Client ID
Create a Google Client ID by:
- Logging into the Google API Console
- Creating a new project
- Enabling OAuth and configuring the consent screen
Follow this guide for detailed instructions.Your client ID will look like: 1234567890-abc123def456.apps.googleusercontent.com Configure IHP
Open Config/Config.hs and add:import IHP.OAuth.Google.Config
config :: ConfigBuilder
config = do
option Development
option (AppHostname "localhost")
initGoogleOAuth -- <--- ADD THIS
Set environment variable
Open your start script and add:export OAUTH_GOOGLE_CLIENT_ID="YOUR_GOOGLE_CLIENT_ID"
RunDevServer
Replace YOUR_GOOGLE_CLIENT_ID with your actual client ID. Restart dev server
Restart your local development server to apply the changes.
Testing
Visit http://localhost:8000/NewSessionWithGoogle and click the “Login with Google” button. It should open a Google login dialog and then log you into the app.
If you see an error, check the JavaScript console. A common issue is that localhost:8000 isn’t whitelisted in your Google Client ID settings.
To add a “Login with Google” button to your login page, open Web/View/Sessions/New.hs:
module Web.View.Sessions.New where
import Web.View.Prelude
import IHP.AuthSupport.View.Sessions.New
instance View (NewView User) where
html NewView { .. } = [hsx|
<div class="h-100" id="sessions-new">
<div class="d-flex align-items-center">
<div class="w-100">
<div style="max-width: 400px" class="mx-auto mb-5">
<h5>Please login</h5>
{renderForm user}
{loginWithGoogle}
</div>
</div>
</div>
</div>
|]
loginWithGoogle :: Html
loginWithGoogle = [hsx|
<div id="g_id_onload"
data-client_id={googleClientId}
data-context="signin"
data-ux_mode="popup"
data-callback="onGoogleLogin"
data-auto_prompt="false">
</div>
<div class="g_id_signin"
data-type="standard"
data-shape="rectangular"
data-theme="outline"
data-text="continue_with"
data-size="large"
data-logo_alignment="left"
data-width="304">
</div>
<form method="POST" action={GoogleConnectCallbackAction} id="new-session-with-google-form">
<input type="hidden" name="jwt" value=""/>
</form>
<script src="/google-login.js?v2"></script>
<script src="https://accounts.google.com/gsi/client"></script>
|]
where
googleClientId :: Text = "YOUR GOOGLE CLIENT ID"
Create static/google-login.js:
function onGoogleLogin(response) {
var form = document.getElementById('new-session-with-google-form');
form.querySelector('input[name="jwt"]').value = response.credential;
form.submit();
}
Troubleshooting Docker/Nix Images
If Google login works locally but fails in Docker with a certificate error:
HttpExceptionRequest ... (InternalException (HandshakeFailed (Error_Protocol "certificate has unknown CA" UnknownCa)))
Your image is missing root CA certificates. Include CA certificates and set SSL_CERT_FILE/NIX_SSL_CERT_FILE environment variables in your Docker image.
GitHub OAuth
Installation
Add the dependency
Open your project’s default.nix and add ihp-oauth-github:let
ihp = ..
haskellEnv = import "${ihp}/NixSupport/default.nix" {
ihp = ihp;
haskellDeps = p: with p; [
# ...
ihp-oauth-github # <----- ADD THIS LINE
];
otherDeps = p: with p; [
];
projectPath = ./.;
};
in
haskellEnv
Install the package
Stop your development server and run:
Database Schema
Add a github_user_id column to your users table. Open Application/Schema.sql:
CREATE TABLE users (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
-- ... ,
github_user_id INT DEFAULT NULL UNIQUE
);
Controller Setup
Create Web/Controller/GithubOAuth.hs:
module Web.Controller.GithubOAuth where
import Web.Controller.Prelude
import Web.Controller.Sessions ()
import IHP.OAuth.Github.Controller
import qualified IHP.OAuth.Github.Types as Github
instance Controller Github.GithubOAuthController where
action Github.NewSessionWithGithubAction = newSessionWithGithubAction @User
action Github.GithubConnectCallbackAction = githubConnectCallbackAction @User
instance GithubOAuthControllerConfig User where
beforeCreateUser user githubUser = user
|> set #isConfirmed True -- Auto-confirm email
afterCreateUser user = do
-- Called after a new user is created
-- E.g. send a welcome email:
-- sendMail WelcomeMail { .. }
pure ()
beforeLogin user githubUser = do
-- Called before the user is logged in
-- E.g. set profile picture from GitHub:
-- user
-- |> setJust #profilePicture githubUser.avatarUrl
-- |> setJust #githubName githubUser.login
-- |> pure
pure user
Routing
Open Web/Routes.hs and:
-
Import the type:
import IHP.OAuth.Github.Types
-
Enable AutoRoute:
instance AutoRoute GithubOAuthController
FrontController
Open Web/FrontController.hs and:
-
Add imports:
import IHP.OAuth.Github.Types
import Web.Controller.GithubOAuth
-
Add the controller:
instance FrontController WebApplication where
controllers =
[ startPage StartpageAction
-- ...
, parseRoute @GithubOAuthController -- <----- ADD THIS
]
View Prelude
Update Web/View/Prelude.hs:
module Web.View.Prelude
( module IHP.ViewPrelude
-- ...
, module IHP.OAuth.Github.Types -- <---- ADD THIS
) where
import IHP.ViewPrelude
-- ...
import IHP.OAuth.Github.Types -- <---- ADD THIS
Configuration
Create GitHub App
Follow GitHub’s guide to create a GitHub App.Set the Callback URL to: http://localhost:8000/GithubConnectCallbackSave your Client ID and Client Secret. Configure IHP
Open Config/Config.hs and add:import IHP.OAuth.Github.Config
config :: ConfigBuilder
config = do
option Development
option (AppHostname "localhost")
initGithubOAuth -- <--- ADD THIS
Set environment variables
Open your start script and add:export OAUTH_GITHUB_CLIENT_ID="YOUR_GITHUB_CLIENT_ID"
export OAUTH_GITHUB_CLIENT_SECRET="YOUR_GITHUB_SECRET"
RunDevServer
Replace the placeholders with your actual values. Restart dev server
Restart your local development server.
Testing
Visit http://localhost:8000/NewSessionWithGithub and click “Login with Github”. It will redirect to GitHub for authentication and then log you into your app.
Add a GitHub login button to your login page:
<a href={NewSessionWithGithubAction} target="_self" class="btn btn-primary">
Continue with GitHub
</a>
Use target="_self" to prevent Turbolinks from interfering with the GitHub redirect.
Customization
Email Confirmation
By default, the examples auto-confirm user emails:
beforeCreateUser user googleClaims = user
|> set #isConfirmed True
Remove this line if you want to require email confirmation.
Custom User Data
Use the lifecycle hooks to customize user creation:
instance GithubOAuthControllerConfig User where
beforeCreateUser user githubUser = user
|> set #isConfirmed True
|> setJust #name githubUser.name
|> setJust #profilePicture githubUser.avatarUrl
afterCreateUser user = do
sendMail WelcomeMail { .. }
pure ()
beforeLogin user githubUser = do
-- Update profile picture on each login
user
|> setJust #profilePicture githubUser.avatarUrl
|> updateRecord
Best Practices
- Environment Variables: Never hardcode OAuth credentials in your code
- HTTPS in Production: Always use HTTPS for OAuth callbacks in production
- Scope Management: Request only the OAuth scopes you need
- Error Handling: Implement proper error handling for OAuth failures
- User Linking: Consider allowing users to link multiple OAuth providers to one account
Security Considerations
- Validate Callback URLs: Ensure your OAuth callback URLs are properly configured
- CSRF Protection: IHP handles CSRF protection automatically
- State Parameter: OAuth implementations use the state parameter for security
- Secure Storage: OAuth tokens are not stored by default; only user IDs are saved
See Also