Module: BetterAuth::SocialProviders
- Defined in:
- lib/better_auth/social_providers/vk.rb,
lib/better_auth/social_providers/base.rb,
lib/better_auth/social_providers/kick.rb,
lib/better_auth/social_providers/line.rb,
lib/better_auth/social_providers/zoom.rb,
lib/better_auth/social_providers/apple.rb,
lib/better_auth/social_providers/figma.rb,
lib/better_auth/social_providers/kakao.rb,
lib/better_auth/social_providers/naver.rb,
lib/better_auth/social_providers/polar.rb,
lib/better_auth/social_providers/slack.rb,
lib/better_auth/social_providers/github.rb,
lib/better_auth/social_providers/gitlab.rb,
lib/better_auth/social_providers/google.rb,
lib/better_auth/social_providers/linear.rb,
lib/better_auth/social_providers/notion.rb,
lib/better_auth/social_providers/paybin.rb,
lib/better_auth/social_providers/paypal.rb,
lib/better_auth/social_providers/reddit.rb,
lib/better_auth/social_providers/roblox.rb,
lib/better_auth/social_providers/tiktok.rb,
lib/better_auth/social_providers/twitch.rb,
lib/better_auth/social_providers/vercel.rb,
lib/better_auth/social_providers/wechat.rb,
lib/better_auth/social_providers/cognito.rb,
lib/better_auth/social_providers/discord.rb,
lib/better_auth/social_providers/dropbox.rb,
lib/better_auth/social_providers/railway.rb,
lib/better_auth/social_providers/spotify.rb,
lib/better_auth/social_providers/twitter.rb,
lib/better_auth/social_providers/facebook.rb,
lib/better_auth/social_providers/linkedin.rb,
lib/better_auth/social_providers/atlassian.rb,
lib/better_auth/social_providers/salesforce.rb,
lib/better_auth/social_providers/huggingface.rb,
lib/better_auth/social_providers/microsoft_entra_id.rb
Defined Under Namespace
Modules: Base
Class Method Summary collapse
- .apple(client_id:, client_secret:, scopes: ["email", "name"], **options) ⇒ Object
- .atlassian(client_id:, client_secret:, scopes: ["read:jira-user", "offline_access"], **options) ⇒ Object
- .cognito(client_id:, client_secret: nil, scopes: ["openid", "profile", "email"], **options) ⇒ Object
- .discord(client_id:, client_secret:, scopes: ["identify", "email"], **options) ⇒ Object
- .discord_avatar_url(profile) ⇒ Object
- .dropbox(client_id:, client_secret:, scopes: ["account_info.read"], **options) ⇒ Object
- .facebook(client_id:, client_secret:, scopes: ["email", "public_profile"], **options) ⇒ Object
- .figma(client_id:, client_secret:, scopes: ["current_user:read"], **options) ⇒ Object
- .github(client_id:, client_secret:, scopes: ["read:user", "user:email"], **options) ⇒ Object
- .gitlab(client_id:, client_secret:, issuer: "https://gitlab.com", scopes: ["read_user"], **options) ⇒ Object
- .google(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **options) ⇒ Object
- .huggingface(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **options) ⇒ Object
- .kakao(client_id:, client_secret:, scopes: ["account_email", "profile_image", "profile_nickname"], **options) ⇒ Object
- .kick(client_id:, client_secret:, scopes: ["user:read"], **options) ⇒ Object
- .line(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **options) ⇒ Object
- .linear(client_id:, client_secret:, scopes: ["read"], **options) ⇒ Object
- .linkedin(client_id:, client_secret:, scopes: ["profile", "email", "openid"], **options) ⇒ Object
- .microsoft(client_id:, client_secret: nil, tenant_id: "common", scopes: ["openid", "profile", "email", "User.Read", "offline_access"], **options) ⇒ Object
- .microsoft_email_verified?(profile, email) ⇒ Boolean
- .microsoft_entra_id(client_id:, client_secret:, tenant_id: "common", scopes: ["openid", "profile", "email", "User.Read", "offline_access"], **options) ⇒ Object
- .microsoft_provider(provider_id:, provider_name:, client_id:, client_secret:, tenant_id:, scopes:, **options) ⇒ Object
- .naver(client_id:, client_secret:, scopes: ["profile", "email"], **options) ⇒ Object
- .notion(client_id:, client_secret:, scopes: [], **options) ⇒ Object
- .paybin(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **options) ⇒ Object
- .paypal(client_id:, client_secret:, scopes: [], **options) ⇒ Object
- .polar(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **options) ⇒ Object
- .railway(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **options) ⇒ Object
- .reddit(client_id:, client_secret:, scopes: ["identity"], **options) ⇒ Object
- .roblox(client_id:, client_secret:, scopes: ["openid", "profile"], **options) ⇒ Object
- .salesforce(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **options) ⇒ Object
- .slack(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **options) ⇒ Object
- .spotify(client_id:, client_secret:, scopes: ["user-read-email"], **options) ⇒ Object
- .tiktok(client_id:, client_secret:, scopes: ["user.info.profile"], **options) ⇒ Object
- .twitch(client_id:, client_secret:, scopes: ["user:read:email", "openid"], **options) ⇒ Object
- .twitter(client_id:, client_secret:, scopes: ["users.read", "tweet.read", "offline.access", "users.email"], **options) ⇒ Object
- .vercel(client_id:, client_secret:, scopes: [], **options) ⇒ Object
- .vk(client_id:, client_secret:, scopes: ["email", "phone"], **options) ⇒ Object
- .wechat(client_id:, client_secret:, scopes: ["snsapi_login"], **options) ⇒ Object
- .zoom(client_id:, client_secret:, scopes: [], **options) ⇒ Object
Class Method Details
.apple(client_id:, client_secret:, scopes: ["email", "name"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/better_auth/social_providers/apple.rb', line 7 def apple(client_id:, client_secret:, scopes: ["email", "name"], **) normalized = Base.() primary_client_id = Base.primary_client_id(client_id) { id: "apple", name: "Apple", client_id: client_id, client_secret: client_secret, create_authorization_url: lambda do |data| Base.([:authorization_endpoint] || "https://appleid.apple.com/auth/authorize", { client_id: primary_client_id, redirect_uri: data[:redirect_uri] || data[:redirectURI], response_type: "code id_token", response_mode: [:response_mode] || [:responseMode] || "form_post", scope: Base.selected_scopes(scopes, normalized, data), state: data[:state] }) end, validate_authorization_code: lambda do |data| Base.post_form("https://appleid.apple.com/auth/token", { client_id: primary_client_id, client_secret: client_secret, code: data[:code], code_verifier: data[:code_verifier] || data[:codeVerifier], grant_type: "authorization_code", redirect_uri: data[:redirect_uri] || data[:redirectURI] }) end, verify_id_token: normalized[:verify_id_token] || lambda do |token, nonce = nil| return false if normalized[:disable_id_token_sign_in] audiences = Array(normalized[:audience] || normalized[:app_bundle_identifier] || normalized[:appBundleIdentifier] || client_id) return false if audiences.empty? profile = Base.verify_jwt_with_jwks( token, jwks: normalized[:jwks], jwks_endpoint: normalized[:jwks_endpoint] || "https://appleid.apple.com/auth/keys", algorithms: ["RS256"], issuers: "https://appleid.apple.com", audience: audiences, nonce: nonce ) !!profile&.fetch("sub", nil) end, get_user_info: lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom profile = Base.decode_jwt_payload(Base.id_token(tokens)) apple_user = tokens[:user] || tokens["user"] || {} name = apple_user.dig(:name, :firstName) || apple_user.dig(:name, :first_name) || apple_user.dig("name", "firstName") || apple_user.dig("name", "first_name") last_name = apple_user.dig(:name, :lastName) || apple_user.dig(:name, :last_name) || apple_user.dig("name", "lastName") || apple_user.dig("name", "last_name") full_name = [name, last_name].compact.join(" ").strip full_name = profile["name"] || "" if full_name.empty? user = Base.apply_profile_mapping( { id: profile["sub"], email: profile["email"], name: full_name, image: profile["picture"], emailVerified: profile["email_verified"] == true || profile["email_verified"] == "true" }, profile.merge("name" => full_name), normalized ) { user: user, data: profile.merge("name" => full_name) } end, refresh_access_token: [:refresh_access_token] || [:refreshAccessToken] || lambda do |refresh_token| Base.refresh_access_token("https://appleid.apple.com/auth/token", refresh_token, client_id: primary_client_id, client_secret: client_secret) end } end |
.atlassian(client_id:, client_secret:, scopes: ["read:jira-user", "offline_access"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/better_auth/social_providers/atlassian.rb', line 7 def atlassian(client_id:, client_secret:, scopes: ["read:jira-user", "offline_access"], **) Base.oauth_provider( id: "atlassian", name: "Atlassian", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://auth.atlassian.com/authorize", token_endpoint: "https://auth.atlassian.com/oauth/token", user_info_endpoint: "https://api.atlassian.com/me", scopes: scopes, pkce: true, auth_params: {audience: "api.atlassian.com"}, profile_map: ->(profile) { { id: profile["account_id"], name: profile["name"], email: profile["email"], image: profile["picture"], emailVerified: false } }, ** ) end |
.cognito(client_id:, client_secret: nil, scopes: ["openid", "profile", "email"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/better_auth/social_providers/cognito.rb', line 7 def cognito(client_id:, client_secret: nil, scopes: ["openid", "profile", "email"], **) domain = ([:domain] || [:issuer] || "https://cognito-idp.#{[:region] || "us-east-1"}.amazonaws.com").to_s.sub(%r{/+\z}, "") Base.oauth_provider( id: "cognito", name: "Cognito", client_id: client_id, client_secret: client_secret, authorization_endpoint: "#{domain}/oauth2/authorize", token_endpoint: "#{domain}/oauth2/token", user_info_endpoint: "#{domain}/oauth2/userinfo", scopes: scopes, pkce: true, profile_map: ->(profile) { { id: profile["sub"], name: profile["name"] || profile["given_name"] || profile["username"] || "", email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] } }, ** ) end |
.discord(client_id:, client_secret:, scopes: ["identify", "email"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/better_auth/social_providers/discord.rb', line 7 def discord(client_id:, client_secret:, scopes: ["identify", "email"], **) normalized = Base.() { id: "discord", name: "Discord", client_id: client_id, client_secret: client_secret, create_authorization_url: lambda do |data| selected_scopes = Base.selected_scopes(scopes, normalized, data) params = { client_id: client_id, redirect_uri: data[:redirect_uri] || data[:redirectURI], response_type: "code", scope: selected_scopes, state: data[:state], prompt: .fetch(:prompt, "none") } params[:permissions] = [:permissions] if selected_scopes.include?("bot") && .key?(:permissions) Base.("https://discord.com/api/oauth2/authorize", params) end, validate_authorization_code: lambda do |data| Base.post_form("https://discord.com/api/oauth2/token", { client_id: client_id, client_secret: client_secret, code: data[:code], grant_type: "authorization_code", redirect_uri: data[:redirect_uri] || data[:redirectURI] }) end, get_user_info: lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom profile = Base.get_json("https://discord.com/api/users/@me", "Authorization" => "Bearer #{Base.access_token(tokens)}") image = discord_avatar_url(profile) profile["image_url"] = image user = Base.apply_profile_mapping( { id: profile["id"], email: profile["email"], name: profile["global_name"] || profile["username"] || "", image: image, emailVerified: !!profile["verified"] }, profile, normalized ) { user: user, data: profile } end, refresh_access_token: [:refresh_access_token] || [:refreshAccessToken] || lambda do |refresh_token| Base.refresh_access_token("https://discord.com/api/oauth2/token", refresh_token, client_id: client_id, client_secret: client_secret) end } end |
.discord_avatar_url(profile) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/better_auth/social_providers/discord.rb', line 65 def discord_avatar_url(profile) avatar = profile["avatar"] unless avatar discriminator = profile["discriminator"].to_s default_avatar_number = if discriminator == "0" (profile["id"].to_i >> 22) % 6 else discriminator.to_i % 5 end return "https://cdn.discordapp.com/embed/avatars/#{default_avatar_number}.png" end format = avatar.start_with?("a_") ? "gif" : "png" "https://cdn.discordapp.com/avatars/#{profile["id"]}/#{avatar}.#{format}" end |
.dropbox(client_id:, client_secret:, scopes: ["account_info.read"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/better_auth/social_providers/dropbox.rb', line 7 def dropbox(client_id:, client_secret:, scopes: ["account_info.read"], **) Base.oauth_provider( id: "dropbox", name: "Dropbox", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://www.dropbox.com/oauth2/authorize", token_endpoint: "https://api.dropboxapi.com/oauth2/token", user_info_endpoint: "https://api.dropboxapi.com/2/users/get_current_account", user_info_method: :post, scopes: scopes, pkce: true, auth_params: ->(_data, opts) { {token_access_type: opts[:access_type] || opts[:accessType]} }, profile_map: ->(profile) { { id: profile["account_id"], name: profile.dig("name", "display_name"), email: profile["email"], image: profile["profile_photo_url"], emailVerified: !!profile["email_verified"] } }, ** ) end |
.facebook(client_id:, client_secret:, scopes: ["email", "public_profile"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/better_auth/social_providers/facebook.rb', line 7 def facebook(client_id:, client_secret:, scopes: ["email", "public_profile"], **) fields = Array([:fields] || %w[id name email picture email_verified]).join(",") provider = Base.oauth_provider( id: "facebook", name: "Facebook", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://www.facebook.com/v24.0/dialog/oauth", token_endpoint: "https://graph.facebook.com/v24.0/oauth/access_token", user_info_endpoint: "https://graph.facebook.com/me?fields=#{URI.encode_www_form_component(fields)}", scopes: scopes, auth_params: ->(_data, opts) { {config_id: opts[:config_id] || opts[:configId]} }, profile_map: ->(profile) { picture = profile.dig("picture", "data", "url") || profile["picture"] { id: profile["id"] || profile["sub"], name: profile["name"], email: profile["email"], image: picture, emailVerified: !!profile["email_verified"] } }, ** ) provider[:verify_id_token] = provider[:options][:verify_id_token] || ->(token, _nonce = nil) { provider[:options][:disable_id_token_sign_in] ? false : !token.to_s.empty? } provider end |
.figma(client_id:, client_secret:, scopes: ["current_user:read"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/better_auth/social_providers/figma.rb', line 7 def figma(client_id:, client_secret:, scopes: ["current_user:read"], **) Base.oauth_provider( id: "figma", name: "Figma", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://www.figma.com/oauth", token_endpoint: "https://api.figma.com/v1/oauth/token", user_info_endpoint: "https://api.figma.com/v1/me", scopes: scopes, pkce: true, profile_map: ->(profile) { { id: profile["id"], name: profile["handle"], email: profile["email"], image: profile["img_url"], emailVerified: false } }, ** ) end |
.github(client_id:, client_secret:, scopes: ["read:user", "user:email"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/better_auth/social_providers/github.rb', line 7 def github(client_id:, client_secret:, scopes: ["read:user", "user:email"], **) normalized = Base.() token_endpoint = normalized[:token_endpoint] || "https://github.com/login/oauth/access_token" user_info_endpoint = normalized[:user_info_endpoint] || "https://api.github.com/user" emails_endpoint = normalized[:emails_endpoint] || "https://api.github.com/user/emails" { id: "github", name: "GitHub", client_id: client_id, client_secret: client_secret, create_authorization_url: lambda do |data| Base.([:authorization_endpoint] || "https://github.com/login/oauth/authorize", { client_id: client_id, redirect_uri: data[:redirect_uri] || data[:redirectURI], scope: Base.selected_scopes(scopes, normalized, data), state: data[:state], login_hint: data[:loginHint] || data[:login_hint], prompt: [:prompt] }) end, validate_authorization_code: lambda do |data| Base.post_form(token_endpoint, { client_id: client_id, client_secret: client_secret, code: data[:code], code_verifier: data[:code_verifier] || data[:codeVerifier], redirect_uri: data[:redirect_uri] || data[:redirectURI] }) end, get_user_info: lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom headers = { "Authorization" => "Bearer #{Base.access_token(tokens)}", "Accept" => "application/json", "User-Agent" => "better-auth" } profile = Base.get_json(user_info_endpoint, headers) emails = Base.get_json(emails_endpoint, headers) primary = Array(emails).find { |email| email["email"] == profile["email"] } || Array(emails).find { |email| email["primary"] } || Array(emails).first || {} user = Base.apply_profile_mapping( { id: profile["id"].to_s, email: profile["email"] || primary["email"], name: profile["name"] || profile["login"], image: profile["avatar_url"], emailVerified: !!primary["verified"] }, profile, normalized ) { user: user, data: profile } end, refresh_access_token: [:refresh_access_token] || [:refreshAccessToken] || lambda do |refresh_token| Base.refresh_access_token(token_endpoint, refresh_token, client_id: client_id, client_secret: client_secret) end } end |
.gitlab(client_id:, client_secret:, issuer: "https://gitlab.com", scopes: ["read_user"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/better_auth/social_providers/gitlab.rb', line 7 def gitlab(client_id:, client_secret:, issuer: "https://gitlab.com", scopes: ["read_user"], **) base = issuer.to_s.sub(%r{/+\z}, "") normalized = Base.() { id: "gitlab", name: "GitLab", client_id: client_id, client_secret: client_secret, create_authorization_url: lambda do |data| Base.([:authorization_endpoint] || "#{base}/oauth/authorize", { client_id: client_id, redirect_uri: data[:redirect_uri] || data[:redirectURI], response_type: "code", scope: Base.selected_scopes(scopes, normalized, data), state: data[:state], code_challenge: (data[:code_verifier] || data[:codeVerifier]) && Base.pkce_challenge(data[:code_verifier] || data[:codeVerifier]), code_challenge_method: (data[:code_verifier] || data[:codeVerifier]) && "S256", login_hint: data[:loginHint] || data[:login_hint] }) end, validate_authorization_code: lambda do |data| Base.post_form("#{base}/oauth/token", { client_id: client_id, client_secret: client_secret, code: data[:code], code_verifier: data[:code_verifier] || data[:codeVerifier], grant_type: "authorization_code", redirect_uri: data[:redirect_uri] || data[:redirectURI] }) end, get_user_info: lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom profile = Base.get_json("#{base}/api/v4/user", "Authorization" => "Bearer #{Base.access_token(tokens)}") return nil if profile["state"] && profile["state"] != "active" return nil if profile["locked"] == true user = Base.apply_profile_mapping( { id: profile["id"].to_s, email: profile["email"], name: profile["name"] || profile["username"], image: profile["avatar_url"], emailVerified: !!profile["email_verified"] }, profile, normalized ) { user: user, data: profile } end, refresh_access_token: [:refresh_access_token] || [:refreshAccessToken] || lambda do |refresh_token| Base.refresh_access_token("#{base}/oauth/token", refresh_token, client_id: client_id, client_secret: client_secret) end } end |
.google(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/better_auth/social_providers/google.rb', line 7 def google(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **) normalized = Base.() primary_client_id = Base.primary_client_id(client_id) { id: "google", name: "Google", client_id: client_id, client_secret: client_secret, create_authorization_url: lambda do |data| verifier = data[:code_verifier] || data[:codeVerifier] raise Error, "codeVerifier is required for Google" if verifier.to_s.empty? Base.([:authorization_endpoint] || "https://accounts.google.com/o/oauth2/v2/auth", { client_id: primary_client_id, redirect_uri: data[:redirect_uri] || data[:redirectURI], response_type: "code", scope: Base.selected_scopes(scopes, normalized, data), state: data[:state], code_challenge: verifier && Base.pkce_challenge(verifier), code_challenge_method: verifier && "S256", login_hint: data[:loginHint] || data[:login_hint], prompt: [:prompt], access_type: [:access_type] || [:accessType] || "offline", display: data[:display] || [:display], hd: [:hd], include_granted_scopes: "true" }) end, validate_authorization_code: lambda do |data| Base.post_form("https://oauth2.googleapis.com/token", { client_id: primary_client_id, client_secret: client_secret, code: data[:code], code_verifier: data[:code_verifier] || data[:codeVerifier], grant_type: "authorization_code", redirect_uri: data[:redirect_uri] || data[:redirectURI] }) end, verify_id_token: normalized[:verify_id_token] || lambda do |token, nonce = nil| return false if normalized[:disable_id_token_sign_in] audiences = Array(client_id) return false if audiences.empty? profile = Base.verify_jwt_with_jwks( token, jwks: normalized[:jwks], jwks_endpoint: normalized[:jwks_endpoint] || "https://www.googleapis.com/oauth2/v3/certs", algorithms: ["RS256"], issuers: ["https://accounts.google.com", "accounts.google.com"], audience: audiences, nonce: nonce ) !!profile&.fetch("sub", nil) end, get_user_info: lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom next nil unless Base.id_token(tokens) profile = Base.decode_jwt_payload(Base.id_token(tokens)) user = Base.apply_profile_mapping( { id: profile["sub"], email: profile["email"], name: profile["name"], image: profile["picture"], emailVerified: !!profile["email_verified"] }, profile, normalized ) { user: user, data: profile } end, refresh_access_token: [:refresh_access_token] || [:refreshAccessToken] || lambda do |refresh_token| Base.refresh_access_token("https://oauth2.googleapis.com/token", refresh_token, client_id: primary_client_id, client_secret: client_secret) end } end |
.huggingface(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/better_auth/social_providers/huggingface.rb', line 7 def huggingface(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **) Base.oauth_provider( id: "huggingface", name: "Hugging Face", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://huggingface.co/oauth/authorize", token_endpoint: "https://huggingface.co/oauth/token", user_info_endpoint: "https://huggingface.co/oauth/userinfo", scopes: scopes, pkce: true, profile_map: ->(profile) { { id: profile["sub"], name: profile["name"] || profile["preferred_username"] || "", email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] } }, ** ) end |
.kakao(client_id:, client_secret:, scopes: ["account_email", "profile_image", "profile_nickname"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/better_auth/social_providers/kakao.rb', line 7 def kakao(client_id:, client_secret:, scopes: ["account_email", "profile_image", "profile_nickname"], **) Base.oauth_provider( id: "kakao", name: "Kakao", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://kauth.kakao.com/oauth/authorize", token_endpoint: "https://kauth.kakao.com/oauth/token", user_info_endpoint: "https://kapi.kakao.com/v2/user/me", scopes: scopes, profile_map: ->(profile) { account = profile["kakao_account"] || {} kakao_profile = account["profile"] || {} { id: profile["id"].to_s, name: kakao_profile["nickname"] || account["name"] || "", email: account["email"], image: kakao_profile["profile_image_url"] || kakao_profile["thumbnail_image_url"], emailVerified: !!account["is_email_valid"] && !!account["is_email_verified"] } }, ** ) end |
.kick(client_id:, client_secret:, scopes: ["user:read"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/better_auth/social_providers/kick.rb', line 7 def kick(client_id:, client_secret:, scopes: ["user:read"], **) Base.oauth_provider( id: "kick", name: "Kick", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://id.kick.com/oauth/authorize", token_endpoint: "https://id.kick.com/oauth/token", user_info_endpoint: "https://api.kick.com/public/v1/users", scopes: scopes, pkce: true, profile_map: ->(profile) { user = Array(profile["data"]).first || profile { id: user["user_id"], name: user["name"], email: user["email"], image: user["profile_picture"], emailVerified: false } }, ** ) end |
.line(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/better_auth/social_providers/line.rb', line 7 def line(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **) provider = Base.oauth_provider( id: "line", name: "LINE", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://access.line.me/oauth2/v2.1/authorize", token_endpoint: "https://api.line.me/oauth2/v2.1/token", user_info_endpoint: "https://api.line.me/oauth2/v2.1/userinfo", scopes: scopes, pkce: true, profile_map: ->(profile) { { id: profile["sub"] || profile["userId"], name: profile["name"] || profile["displayName"] || "", email: profile["email"], image: profile["picture"] || profile["pictureUrl"], emailVerified: false } }, ** ) provider[:verify_id_token] = provider[:options][:verify_id_token] || ->(token, _nonce = nil) { provider[:options][:disable_id_token_sign_in] ? false : !Base.decode_jwt_payload(token).empty? } provider end |
.linear(client_id:, client_secret:, scopes: ["read"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/better_auth/social_providers/linear.rb', line 7 def linear(client_id:, client_secret:, scopes: ["read"], **) provider = Base.oauth_provider( id: "linear", name: "Linear", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://linear.app/oauth/authorize", token_endpoint: "https://api.linear.app/oauth/token", scopes: scopes, profile_map: ->(profile) { viewer = profile.dig("data", "viewer") || {} { id: viewer["id"], name: viewer["name"], email: viewer["email"], image: viewer["avatarUrl"], emailVerified: false } }, ** ) provider[:get_user_info] = lambda do |tokens| custom = Base.option(provider[:options], :get_user_info) profile = custom ? custom.call(tokens) : Base.post_json( "https://api.linear.app/graphql", {query: "{ viewer { id name email avatarUrl active createdAt updatedAt } }"}, "Authorization" => "Bearer #{Base.access_token(tokens)}" ) return profile if Base.provider_user_info?(profile) mapped = provider[:options][:map_profile_to_user]&.call(profile) || {} viewer = profile.dig("data", "viewer") || {} {user: {id: viewer["id"], name: viewer["name"], email: viewer["email"], image: viewer["avatarUrl"], emailVerified: false}.merge(mapped), data: profile} end provider end |
.linkedin(client_id:, client_secret:, scopes: ["profile", "email", "openid"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/better_auth/social_providers/linkedin.rb', line 7 def linkedin(client_id:, client_secret:, scopes: ["profile", "email", "openid"], **) Base.oauth_provider( id: "linkedin", name: "Linkedin", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://www.linkedin.com/oauth/v2/authorization", token_endpoint: "https://www.linkedin.com/oauth/v2/accessToken", user_info_endpoint: "https://api.linkedin.com/v2/userinfo", scopes: scopes, profile_map: ->(profile) { { id: profile["sub"], name: profile["name"], email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] } }, ** ) end |
.microsoft(client_id:, client_secret: nil, tenant_id: "common", scopes: ["openid", "profile", "email", "User.Read", "offline_access"], **options) ⇒ Object
20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/better_auth/social_providers/microsoft_entra_id.rb', line 20 def microsoft(client_id:, client_secret: nil, tenant_id: "common", scopes: ["openid", "profile", "email", "User.Read", "offline_access"], **) normalized = Base.() microsoft_provider( provider_id: "microsoft", provider_name: "Microsoft EntraID", client_id: client_id, client_secret: client_secret, tenant_id: normalized[:tenant_id] || tenant_id, scopes: scopes, ** ) end |
.microsoft_email_verified?(profile, email) ⇒ Boolean
130 131 132 133 134 135 |
# File 'lib/better_auth/social_providers/microsoft_entra_id.rb', line 130 def microsoft_email_verified?(profile, email) return !!profile["email_verified"] if profile.key?("email_verified") Array(profile["verified_primary_email"]).include?(email) || Array(profile["verified_secondary_email"]).include?(email) end |
.microsoft_entra_id(client_id:, client_secret:, tenant_id: "common", scopes: ["openid", "profile", "email", "User.Read", "offline_access"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 |
# File 'lib/better_auth/social_providers/microsoft_entra_id.rb', line 7 def microsoft_entra_id(client_id:, client_secret:, tenant_id: "common", scopes: ["openid", "profile", "email", "User.Read", "offline_access"], **) normalized = Base.() microsoft_provider( provider_id: "microsoft-entra-id", provider_name: "Microsoft Entra ID", client_id: client_id, client_secret: client_secret, tenant_id: normalized[:tenant_id] || tenant_id, scopes: scopes, ** ) end |
.microsoft_provider(provider_id:, provider_name:, client_id:, client_secret:, tenant_id:, scopes:, **options) ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/better_auth/social_providers/microsoft_entra_id.rb', line 33 def microsoft_provider(provider_id:, provider_name:, client_id:, client_secret:, tenant_id:, scopes:, **) = [:authority] || "https://login.microsoftonline.com" base = "#{.to_s.sub(%r{/+\z}, "")}/#{tenant_id}/oauth2/v2.0" normalized = Base.() primary_client_id = Base.primary_client_id(client_id) { id: provider_id, name: provider_name, client_id: client_id, client_secret: client_secret, create_authorization_url: lambda do |data| verifier = data[:code_verifier] || data[:codeVerifier] Base.([:authorization_endpoint] || "#{base}/authorize", { client_id: primary_client_id, redirect_uri: data[:redirect_uri] || data[:redirectURI], response_type: "code", scope: Base.selected_scopes(scopes, normalized, data), state: data[:state], code_challenge: verifier && Base.pkce_challenge(verifier), code_challenge_method: verifier && "S256", login_hint: data[:loginHint] || data[:login_hint], prompt: [:prompt] }) end, validate_authorization_code: lambda do |data| Base.post_form("#{base}/token", { client_id: primary_client_id, client_secret: client_secret, code: data[:code], code_verifier: data[:code_verifier] || data[:codeVerifier], grant_type: "authorization_code", redirect_uri: data[:redirect_uri] || data[:redirectURI] }) end, verify_id_token: normalized[:verify_id_token] || lambda do |token, nonce = nil| return false if normalized[:disable_id_token_sign_in] issuers = nil unless %w[common organizations consumers].include?(tenant_id.to_s) issuers = "#{.to_s.sub(%r{/+\z}, "")}/#{tenant_id}/v2.0" end profile = Base.verify_jwt_with_jwks( token, jwks: normalized[:jwks], jwks_endpoint: normalized[:jwks_endpoint] || "#{.to_s.sub(%r{/+\z}, "")}/#{tenant_id}/discovery/v2.0/keys", algorithms: ["RS256"], issuers: issuers, audience: Array(client_id), nonce: nonce ) !!(profile&.fetch("sub", nil) || profile&.fetch("oid", nil)) end, get_user_info: lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom profile = Base.id_token(tokens) ? Base.decode_jwt_payload(Base.id_token(tokens)) : {} profile = Base.get_json("https://graph.microsoft.com/v1.0/me", "Authorization" => "Bearer #{Base.access_token(tokens)}") if profile.empty? unless normalized[:disable_profile_photo] photo_size = normalized[:profile_photo_size] || 48 photo = Base.get_bytes( "https://graph.microsoft.com/v1.0/me/photos/#{photo_size}x#{photo_size}/$value", "Authorization" => "Bearer #{Base.access_token(tokens)}" ) profile["picture"] = "data:image/jpeg;base64, #{Base64.strict_encode64(photo)}" if photo end email = profile["email"] || profile["mail"] || profile["userPrincipalName"] || profile["preferred_username"] user = Base.apply_profile_mapping( { id: profile["sub"] || profile["id"] || profile["oid"], email: email, name: profile["name"] || profile["displayName"], image: profile["picture"], emailVerified: microsoft_email_verified?(profile, email) }, profile, normalized ) { user: user, data: profile } end, refresh_access_token: [:refresh_access_token] || [:refreshAccessToken] || lambda do |refresh_token| Base.refresh_access_token( "#{base}/token", refresh_token, client_id: primary_client_id, client_secret: client_secret, extra_params: {scope: Base.selected_scopes(scopes, normalized, {}).join(" ")} ) end } end |
.naver(client_id:, client_secret:, scopes: ["profile", "email"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/better_auth/social_providers/naver.rb', line 7 def naver(client_id:, client_secret:, scopes: ["profile", "email"], **) Base.oauth_provider( id: "naver", name: "Naver", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://nid.naver.com/oauth2.0/authorize", token_endpoint: "https://nid.naver.com/oauth2.0/token", user_info_endpoint: "https://openapi.naver.com/v1/nid/me", scopes: scopes, profile_map: ->(profile) { data = profile["response"] || {} { id: data["id"], name: data["name"] || data["nickname"] || "", email: data["email"], image: data["profile_image"], emailVerified: false } }, ** ) end |
.notion(client_id:, client_secret:, scopes: [], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/better_auth/social_providers/notion.rb', line 7 def notion(client_id:, client_secret:, scopes: [], **) Base.oauth_provider( id: "notion", name: "Notion", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://api.notion.com/v1/oauth/authorize", token_endpoint: "https://api.notion.com/v1/oauth/token", user_info_endpoint: "https://api.notion.com/v1/users/me", scopes: scopes, auth_params: {owner: "user"}, user_info_headers: {"Notion-Version" => "2022-06-28"}, profile_map: ->(profile) { user = profile.dig("bot", "owner", "user") || profile { id: user["id"], name: user["name"] || "", email: user.dig("person", "email"), image: user["avatar_url"], emailVerified: false } }, ** ) end |
.paybin(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/better_auth/social_providers/paybin.rb', line 7 def paybin(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **) issuer = ([:issuer] || "https://idp.paybin.io").to_s.sub(%r{/+\z}, "") Base.oauth_provider( id: "paybin", name: "Paybin", client_id: client_id, client_secret: client_secret, authorization_endpoint: "#{issuer}/oauth2/authorize", token_endpoint: "#{issuer}/oauth2/token", scopes: scopes, pkce: true, profile_map: ->(profile) { { id: profile["sub"], name: profile["name"] || profile["preferred_username"] || "", email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] } }, ** ) end |
.paypal(client_id:, client_secret:, scopes: [], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/better_auth/social_providers/paypal.rb', line 7 def paypal(client_id:, client_secret:, scopes: [], **) sandbox = ([:environment] || "sandbox").to_s == "sandbox" auth_host = sandbox ? "https://www.sandbox.paypal.com" : "https://www.paypal.com" api_host = sandbox ? "https://api-m.sandbox.paypal.com" : "https://api-m.paypal.com" provider = Base.oauth_provider( id: "paypal", name: "PayPal", client_id: client_id, client_secret: client_secret, authorization_endpoint: "#{auth_host}/signin/authorize", token_endpoint: "#{api_host}/v1/oauth2/token", user_info_endpoint: "#{api_host}/v1/identity/oauth2/userinfo?schema=paypalv1.1", scopes: scopes, pkce: true, profile_map: ->(profile) { { id: profile["user_id"], name: profile["name"], email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] } }, ** ) provider[:verify_id_token] = provider[:options][:verify_id_token] || ->(token, _nonce = nil) { provider[:options][:disable_id_token_sign_in] ? false : !!Base.decode_jwt_payload(token)["sub"] } provider end |
.polar(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/better_auth/social_providers/polar.rb', line 7 def polar(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **) Base.oauth_provider( id: "polar", name: "Polar", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://polar.sh/oauth2/authorize", token_endpoint: "https://api.polar.sh/v1/oauth2/token", user_info_endpoint: "https://api.polar.sh/v1/oauth2/userinfo", scopes: scopes, pkce: true, profile_map: ->(profile) { { id: profile["id"], name: profile["public_name"] || profile["username"] || "", email: profile["email"], image: profile["avatar_url"], emailVerified: !!profile["email_verified"] } }, ** ) end |
.railway(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/better_auth/social_providers/railway.rb', line 7 def railway(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **) primary_client_id = Base.primary_client_id(client_id) credentials = Base64.strict_encode64("#{primary_client_id}:#{client_secret}") token_endpoint = [:token_endpoint] || [:tokenEndpoint] || "https://backboard.railway.com/oauth/token" provider = Base.oauth_provider( id: "railway", name: "Railway", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://backboard.railway.com/oauth/auth", token_endpoint: "https://backboard.railway.com/oauth/token", user_info_endpoint: "https://backboard.railway.com/oauth/me", scopes: scopes, pkce: true, profile_map: ->(profile) { { id: profile["sub"], name: profile["name"], email: profile["email"], image: profile["picture"], emailVerified: false } }, ** ) provider[:validate_authorization_code] = lambda do |data| Base.post_form_json(token_endpoint, { code: data[:code], code_verifier: data[:code_verifier] || data[:codeVerifier], grant_type: "authorization_code", redirect_uri: [:redirect_uri] || [:redirectURI] || data[:redirect_uri] || data[:redirectURI] }, {"Authorization" => "Basic #{credentials}"}) end provider[:refresh_access_token] = [:refresh_access_token] || [:refreshAccessToken] || lambda do |refresh_token| Base.normalize_tokens(Base.post_form_json(token_endpoint, { grant_type: "refresh_token", refresh_token: refresh_token }, {"Authorization" => "Basic #{credentials}"})) end provider end |
.reddit(client_id:, client_secret:, scopes: ["identity"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/better_auth/social_providers/reddit.rb', line 7 def reddit(client_id:, client_secret:, scopes: ["identity"], **) Base.oauth_provider( id: "reddit", name: "Reddit", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://www.reddit.com/api/v1/authorize", token_endpoint: "https://www.reddit.com/api/v1/access_token", user_info_endpoint: "https://oauth.reddit.com/api/v1/me", user_info_headers: {"User-Agent" => "better-auth"}, scopes: scopes, auth_params: ->(_data, opts) { {duration: opts[:duration]} }, profile_map: ->(profile) { { id: profile["id"], name: profile["name"], email: profile["oauth_client_id"], image: profile["icon_img"].to_s.split("?").first, emailVerified: !!profile["has_verified_email"] } }, ** ) end |
.roblox(client_id:, client_secret:, scopes: ["openid", "profile"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/better_auth/social_providers/roblox.rb', line 7 def roblox(client_id:, client_secret:, scopes: ["openid", "profile"], **) Base.oauth_provider( id: "roblox", name: "Roblox", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://apis.roblox.com/oauth/v1/authorize", token_endpoint: "https://apis.roblox.com/oauth/v1/token", user_info_endpoint: "https://apis.roblox.com/oauth/v1/userinfo", scopes: scopes, auth_params: ->(_data, opts) { {prompt: opts[:prompt] || "select_account consent"} }, profile_map: ->(profile) { { id: profile["sub"], name: profile["nickname"] || profile["preferred_username"] || "", email: profile["preferred_username"], image: profile["picture"], emailVerified: false } }, ** ) end |
.salesforce(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/better_auth/social_providers/salesforce.rb', line 7 def salesforce(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **) host = if [:loginUrl] || [:login_url] "https://#{[:loginUrl] || [:login_url]}" elsif [:environment].to_s == "sandbox" "https://test.salesforce.com" else "https://login.salesforce.com" end Base.oauth_provider( id: "salesforce", name: "Salesforce", client_id: client_id, client_secret: client_secret, authorization_endpoint: "#{host}/services/oauth2/authorize", token_endpoint: "#{host}/services/oauth2/token", user_info_endpoint: "#{host}/services/oauth2/userinfo", scopes: scopes, pkce: true, profile_map: ->(profile) { { id: profile["user_id"], name: profile["name"], email: profile["email"], image: profile.dig("photos", "picture") || profile.dig("photos", "thumbnail"), emailVerified: !!profile["email_verified"] } }, ** ) end |
.slack(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/better_auth/social_providers/slack.rb', line 7 def slack(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **) Base.oauth_provider( id: "slack", name: "Slack", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://slack.com/openid/connect/authorize", token_endpoint: "https://slack.com/api/openid.connect.token", user_info_endpoint: "https://slack.com/api/openid.connect.userInfo", scopes: scopes, profile_map: ->(profile) { { id: profile["https://slack.com/user_id"] || profile["sub"], name: profile["name"] || "", email: profile["email"], image: profile["picture"] || profile["https://slack.com/user_image_512"], emailVerified: !!profile["email_verified"] } }, ** ) end |
.spotify(client_id:, client_secret:, scopes: ["user-read-email"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/better_auth/social_providers/spotify.rb', line 7 def spotify(client_id:, client_secret:, scopes: ["user-read-email"], **) Base.oauth_provider( id: "spotify", name: "Spotify", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://accounts.spotify.com/authorize", token_endpoint: "https://accounts.spotify.com/api/token", user_info_endpoint: "https://api.spotify.com/v1/me", scopes: scopes, pkce: true, profile_map: ->(profile) { { id: profile["id"], name: profile["display_name"], email: profile["email"], image: Array(profile["images"]).first&.fetch("url", nil), emailVerified: false } }, ** ) end |
.tiktok(client_id:, client_secret:, scopes: ["user.info.profile"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/better_auth/social_providers/tiktok.rb', line 7 def tiktok(client_id:, client_secret:, scopes: ["user.info.profile"], **) client_key = [:client_key] || [:clientKey] || client_id Base.oauth_provider( id: "tiktok", name: "TikTok", client_id: client_key, client_secret: client_secret, authorization_endpoint: "https://www.tiktok.com/v2/auth/authorize", token_endpoint: "https://open.tiktokapis.com/v2/oauth/token/", user_info_endpoint: "https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_large_url,display_name,username", scopes: scopes, scope_separator: ",", auth_params: {client_key: client_key}, token_params: {client_key: client_key}, profile_map: ->(profile) { user = profile.dig("data", "user") || profile { id: user["open_id"], name: user["display_name"] || user["username"] || "", email: user["email"] || user["username"], image: user["avatar_large_url"], emailVerified: false } }, ** ) end |
.twitch(client_id:, client_secret:, scopes: ["user:read:email", "openid"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/better_auth/social_providers/twitch.rb', line 7 def twitch(client_id:, client_secret:, scopes: ["user:read:email", "openid"], **) Base.oauth_provider( id: "twitch", name: "Twitch", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://id.twitch.tv/oauth2/authorize", token_endpoint: "https://id.twitch.tv/oauth2/token", scopes: scopes, auth_params: { claims: JSON.generate({ userinfo: { email: nil, email_verified: nil, preferred_username: nil, picture: nil } }) }, profile_map: ->(profile) { { id: profile["sub"], name: profile["preferred_username"], email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] } }, ** ) end |
.twitter(client_id:, client_secret:, scopes: ["users.read", "tweet.read", "offline.access", "users.email"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/better_auth/social_providers/twitter.rb', line 7 def twitter(client_id:, client_secret:, scopes: ["users.read", "tweet.read", "offline.access", "users.email"], **) Base.oauth_provider( id: "twitter", name: "Twitter", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://x.com/i/oauth2/authorize", token_endpoint: "https://api.x.com/2/oauth2/token", user_info_endpoint: "https://api.x.com/2/users/me?user.fields=profile_image_url,verified", scopes: scopes, pkce: true, profile_map: ->(profile) { data = profile["data"] || profile { id: data["id"], name: data["name"], email: data["email"] || data["username"], image: data["profile_image_url"], emailVerified: !!data["confirmed_email"] } }, ** ) end |
.vercel(client_id:, client_secret:, scopes: [], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/better_auth/social_providers/vercel.rb', line 7 def vercel(client_id:, client_secret:, scopes: [], **) provider = Base.oauth_provider( id: "vercel", name: "Vercel", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://vercel.com/oauth/authorize", token_endpoint: "https://api.vercel.com/login/oauth/token", user_info_endpoint: "https://api.vercel.com/login/oauth/userinfo", scopes: scopes, pkce: true, profile_map: ->(profile) { { id: profile["sub"], name: profile["name"] || profile["preferred_username"] || "", email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] } }, ** ) provider[:create_authorization_url] = lambda do |data| verifier = data[:code_verifier] || data[:codeVerifier] raise Error, "codeVerifier is required for Vercel" if verifier.to_s.empty? selected_scopes = Base.selected_scopes(scopes, Base.(), data) Base.([:authorization_endpoint] || "https://vercel.com/oauth/authorize", { client_id: Base.primary_client_id(client_id), redirect_uri: [:redirect_uri] || [:redirectURI] || data[:redirect_uri] || data[:redirectURI], response_type: "code", scope: selected_scopes.empty? ? nil : selected_scopes, state: data[:state], code_challenge: Base.pkce_challenge(verifier), code_challenge_method: "S256" }) end provider end |
.vk(client_id:, client_secret:, scopes: ["email", "phone"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/better_auth/social_providers/vk.rb', line 7 def vk(client_id:, client_secret:, scopes: ["email", "phone"], **) Base.oauth_provider( id: "vk", name: "VK", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://id.vk.com/authorize", token_endpoint: "https://id.vk.com/oauth2/auth", user_info_endpoint: "https://id.vk.com/oauth2/user_info", user_info_method: :post, user_info_body: {client_id: client_id}, scopes: scopes, pkce: true, profile_map: ->(profile) { user = profile["user"] || profile { id: user["user_id"], name: [user["first_name"], user["last_name"]].compact.join(" "), email: user["email"], image: user["avatar"], emailVerified: false } }, ** ) end |
.wechat(client_id:, client_secret:, scopes: ["snsapi_login"], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/better_auth/social_providers/wechat.rb', line 7 def wechat(client_id:, client_secret:, scopes: ["snsapi_login"], **) normalized = Base.() provider = Base.oauth_provider( id: "wechat", name: "WeChat", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://open.weixin.qq.com/connect/qrconnect", token_endpoint: "https://api.weixin.qq.com/sns/oauth2/access_token", user_info_endpoint: "https://api.weixin.qq.com/sns/userinfo", scopes: scopes, scope_separator: ",", profile_map: ->(profile) { { id: profile["unionid"] || profile["openid"], name: profile["nickname"], email: profile["email"], image: profile["headimgurl"], emailVerified: false } }, ** ) provider[:create_authorization_url] = lambda do |data| "#{Base.("https://open.weixin.qq.com/connect/qrconnect", { appid: client_id, redirect_uri: normalized[:redirect_uri] || data[:redirect_uri] || data[:redirectURI], response_type: "code", scope: Base.selected_scopes(scopes, normalized, data).join(","), state: data[:state], lang: [:lang] || "cn" })}#wechat_redirect" end provider[:validate_authorization_code] = lambda do |data| url = Base.("https://api.weixin.qq.com/sns/oauth2/access_token", { appid: client_id, secret: client_secret, code: data[:code], grant_type: "authorization_code" }) payload = Base.get_json(url) if !payload || payload["errcode"] raise Error, "Failed to validate authorization code: #{payload&.fetch("errmsg", nil) || "Unknown error"}" end Base.normalize_tokens(payload).merge( "openid" => payload["openid"], "unionid" => payload["unionid"] ).compact end provider[:refresh_access_token] = normalized[:refresh_access_token] || lambda do |refresh_token| url = Base.("https://api.weixin.qq.com/sns/oauth2/refresh_token", { appid: client_id, grant_type: "refresh_token", refresh_token: refresh_token }) payload = Base.get_json(url) if !payload || payload["errcode"] raise Error, "Failed to refresh access token: #{payload&.fetch("errmsg", nil) || "Unknown error"}" end Base.normalize_tokens(payload).merge( "openid" => payload["openid"], "unionid" => payload["unionid"] ).compact end provider[:get_user_info] = lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom openid = tokens["openid"] || tokens[:openid] next nil if openid.to_s.empty? url = Base.("https://api.weixin.qq.com/sns/userinfo", { access_token: Base.access_token(tokens), openid: openid, lang: "zh_CN" }) profile = Base.get_json(url) next nil if !profile || profile["errcode"] user = Base.apply_profile_mapping( { id: profile["unionid"] || profile["openid"] || openid, name: profile["nickname"], email: profile["email"], image: profile["headimgurl"], emailVerified: false }, profile, normalized ) {user: user, data: profile} end provider end |
.zoom(client_id:, client_secret:, scopes: [], **options) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/better_auth/social_providers/zoom.rb', line 7 def zoom(client_id:, client_secret:, scopes: [], **) Base.oauth_provider( id: "zoom", name: "Zoom", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://zoom.us/oauth/authorize", token_endpoint: "https://zoom.us/oauth/token", user_info_endpoint: "https://api.zoom.us/v2/users/me", scopes: scopes, pkce: .fetch(:pkce, true), profile_map: ->(profile) { { id: profile["id"], name: profile["display_name"], email: profile["email"], image: profile["pic_url"], emailVerified: !!profile["verified"] } }, ** ) end |