Packages
phoenix_kit
1.7.21
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit_web/users/oauth.ex
if Code.ensure_loaded?(Ueberauth) do
defmodule PhoenixKitWeb.Users.OAuth do
@moduledoc """
OAuth authentication controller using Ueberauth with dynamic provider configuration.
This controller uses `Ueberauth.run_request/4` and `Ueberauth.run_callback/4` for
dynamic OAuth invocation, eliminating compile-time configuration requirements.
OAuth credentials are loaded from database at runtime.
This controller requires the following optional dependencies to be installed:
- ueberauth
- ueberauth_google (for Google Sign-In)
- ueberauth_apple (for Apple Sign-In)
- ueberauth_github (for GitHub Sign-In)
- ueberauth_facebook (for Facebook Sign-In)
If these dependencies are not installed, a fallback controller will be used instead.
"""
use PhoenixKitWeb, :controller
plug PhoenixKitWeb.Plugs.EnsureOAuthScheme
plug PhoenixKitWeb.Plugs.EnsureOAuthConfig
# NOTE: No `plug Ueberauth` - we call Ueberauth.run_request/4 and run_callback/4 dynamically
alias PhoenixKit.Config
alias PhoenixKit.Settings
alias PhoenixKit.Users.OAuth
alias PhoenixKit.Utils.IpAddress
alias PhoenixKit.Utils.Routes
alias PhoenixKitWeb.Users.Auth, as: UserAuth
require Logger
# Map provider names (strings) to strategy modules
@provider_strategies %{
"google" => Ueberauth.Strategy.Google,
"apple" => Ueberauth.Strategy.Apple,
"github" => Ueberauth.Strategy.Github,
"facebook" => Ueberauth.Strategy.Facebook
}
@doc """
Initiates OAuth authentication flow.
Uses `Ueberauth.run_request/4` for dynamic OAuth invocation,
reading credentials from database at runtime.
"""
def request(conn, %{"provider" => provider} = params) do
Logger.debug("PhoenixKit OAuth request for provider: #{provider}")
# Check if OAuth is enabled in settings
if oauth_enabled_in_settings?() do
# Check if provider is supported and enabled
case validate_provider(provider) do
{:ok, strategy_module} ->
handle_oauth_request(conn, provider, strategy_module, params)
{:error, :unknown_provider} ->
Logger.warning("PhoenixKit OAuth: Unknown provider '#{provider}'")
conn
|> put_flash(:error, "Unknown OAuth provider: #{provider}")
|> redirect(to: Routes.path("/users/log-in"))
{:error, :provider_disabled} ->
Logger.warning("PhoenixKit OAuth: Provider '#{provider}' is disabled in settings")
conn
|> put_flash(:error, "OAuth provider '#{provider}' is currently disabled.")
|> redirect(to: Routes.path("/users/log-in"))
{:error, :no_credentials} ->
Logger.warning(
"PhoenixKit OAuth: No credentials configured for provider '#{provider}'"
)
conn
|> put_flash(
:error,
"OAuth provider '#{provider}' is not configured. Please contact your administrator."
)
|> redirect(to: Routes.path("/users/log-in"))
end
else
Logger.warning("PhoenixKit OAuth: OAuth is disabled in settings")
conn
|> put_flash(
:error,
"OAuth authentication is currently disabled. Please contact your administrator to enable it."
)
|> redirect(to: Routes.path("/users/log-in"))
end
end
defp handle_oauth_request(conn, provider, strategy_module, params) do
# Store referral_code and return_to in session
conn =
conn
|> maybe_put_session(:oauth_referral_code, params["referral_code"])
|> maybe_put_session(:oauth_return_to, params["return_to"])
# Build provider config for dynamic Ueberauth call
base_path = Config.UeberAuth.get_base_path()
provider_config =
{strategy_module,
[
request_path: "#{base_path}/#{provider}",
callback_path: "#{base_path}/#{provider}/callback"
]}
# Dynamic Ueberauth call - reads credentials from Application env
# (configured by EnsureOAuthConfig plug)
Ueberauth.run_request(conn, provider, provider_config)
end
defp validate_provider(provider) do
case Map.get(@provider_strategies, provider) do
nil ->
{:error, :unknown_provider}
strategy_module ->
# Check if provider is enabled in settings
if Settings.get_boolean_setting("oauth_#{provider}_enabled", false) do
# Check if credentials exist
if Settings.has_oauth_credentials_direct?(String.to_existing_atom(provider)) do
{:ok, strategy_module}
else
{:error, :no_credentials}
end
else
{:error, :provider_disabled}
end
end
end
defp maybe_put_session(conn, _key, nil), do: conn
defp maybe_put_session(conn, key, value), do: put_session(conn, key, value)
defp oauth_enabled_in_settings? do
Settings.get_boolean_setting("oauth_enabled", false)
end
@doc """
Handles OAuth callback from provider.
Uses `Ueberauth.run_callback/4` for dynamic OAuth invocation,
then processes the result from conn.assigns.
"""
def callback(conn, %{"provider" => provider} = _params) do
Logger.debug("PhoenixKit OAuth callback for provider: #{provider}")
case Map.get(@provider_strategies, provider) do
nil ->
Logger.error("PhoenixKit OAuth: Unknown provider in callback: #{provider}")
conn
|> put_flash(:error, "Unknown OAuth provider: #{provider}")
|> redirect(to: Routes.path("/users/log-in"))
strategy_module ->
# Build provider config for dynamic Ueberauth call
base_path = Config.UeberAuth.get_base_path()
provider_config =
{strategy_module,
[
request_path: "#{base_path}/#{provider}",
callback_path: "#{base_path}/#{provider}/callback"
]}
# Dynamic Ueberauth callback - processes OAuth response
conn = Ueberauth.run_callback(conn, provider, provider_config)
# Handle the result based on assigns set by Ueberauth
handle_callback_result(conn)
end
end
# Handle successful OAuth authentication
defp handle_callback_result(%{assigns: %{ueberauth_auth: auth}} = conn) do
track_geolocation = Settings.get_boolean_setting("track_registration_geolocation", false)
ip_address = IpAddress.extract_from_conn(conn)
referral_code = get_session(conn, :oauth_referral_code)
return_to = get_session(conn, :oauth_return_to)
opts = [
track_geolocation: track_geolocation,
ip_address: ip_address,
referral_code: referral_code
]
case OAuth.handle_oauth_callback(auth, opts) do
{:ok, user} ->
Logger.info(
"PhoenixKit: User #{user.id} (#{user.email}) authenticated via OAuth (#{auth.provider})"
)
conn =
conn
|> delete_session(:oauth_referral_code)
|> delete_session(:oauth_return_to)
flash_message = "Successfully signed in with #{format_provider_name(auth.provider)}!"
conn
|> put_flash(:info, flash_message)
|> UserAuth.log_in_user(user, return_to: return_to)
{:error, %Ecto.Changeset{} = changeset} ->
errors = format_changeset_errors(changeset)
Logger.warning(
"PhoenixKit: OAuth authentication failed for #{auth.info.email}: #{inspect(errors)}"
)
conn
|> put_flash(:error, "Authentication failed: #{errors}")
|> redirect(to: Routes.path("/users/log-in"))
{:error, reason} ->
Logger.error("PhoenixKit: OAuth authentication error: #{inspect(reason)}")
conn
|> put_flash(
:error,
"Authentication failed. Please try again or use a different sign-in method."
)
|> redirect(to: Routes.path("/users/log-in"))
end
end
# Handle OAuth authentication failure
defp handle_callback_result(%{assigns: %{ueberauth_failure: failure}} = conn) do
error_message = format_ueberauth_failure(failure)
Logger.warning("PhoenixKit: OAuth authentication failure: #{inspect(failure)}")
conn
|> put_flash(:error, error_message)
|> redirect(to: Routes.path("/users/log-in"))
end
# Handle unexpected callback without auth or failure
defp handle_callback_result(conn) do
Logger.error("PhoenixKit: Unexpected OAuth callback without auth or failure")
conn
|> put_flash(:error, "Authentication failed. Please try again.")
|> redirect(to: Routes.path("/users/log-in"))
end
# Private helper functions
defp format_provider_name(provider) when is_atom(provider) do
provider |> to_string() |> format_provider_name()
end
defp format_provider_name("google"), do: "Google"
defp format_provider_name("apple"), do: "Apple"
defp format_provider_name("github"), do: "GitHub"
defp format_provider_name("facebook"), do: "Facebook"
defp format_provider_name("twitter"), do: "Twitter"
defp format_provider_name("microsoft"), do: "Microsoft"
defp format_provider_name(provider), do: String.capitalize(provider)
defp format_changeset_errors(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end)
|> Enum.map_join("; ", fn {field, errors} ->
"#{field}: #{Enum.join(errors, ", ")}"
end)
end
defp format_ueberauth_failure(%Ueberauth.Failure{errors: errors}) do
case errors do
[] ->
"Authentication failed. Please try again."
[%{message: message} | _] when is_binary(message) ->
"Authentication failed: #{message}"
_ ->
"Authentication failed. Please try again."
end
end
end
else
# Fallback controller when Ueberauth is not loaded
defmodule PhoenixKitWeb.Users.OAuth do
@moduledoc """
Fallback OAuth controller when Ueberauth dependencies are not installed.
This controller provides user-friendly error messages when OAuth authentication
is attempted but the required dependencies are not installed.
To enable OAuth authentication, add the following dependencies to your mix.exs:
{:ueberauth, "~> 0.10"},
{:ueberauth_google, "~> 0.12"}, # For Google Sign-In
{:ueberauth_apple, "~> 0.1"} # For Apple Sign-In
Then configure the providers in your config.exs as described in the PhoenixKit documentation.
"""
use PhoenixKitWeb, :controller
alias PhoenixKit.Utils.Routes
require Logger
@doc """
Handles OAuth request when Ueberauth is not available.
"""
def request(conn, %{"provider" => provider}) do
Logger.warning(
"PhoenixKit OAuth: Attempted to use #{provider} authentication but Ueberauth dependencies are not installed"
)
conn
|> put_flash(
:error,
"OAuth authentication is not available. Please install the required dependencies (ueberauth, ueberauth_#{provider}) and configure your application. See PhoenixKit documentation for details."
)
|> redirect(to: Routes.path("/users/log-in"))
end
@doc """
Handles OAuth callback when Ueberauth is not available.
"""
def callback(conn, _params) do
Logger.error(
"PhoenixKit OAuth: Received OAuth callback but Ueberauth dependencies are not installed"
)
conn
|> put_flash(
:error,
"OAuth authentication is not configured. Please contact your administrator."
)
|> redirect(to: Routes.path("/users/log-in"))
end
end
end