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
- .google_hosted_domain_allowed?(configured_hosted_domain, token_hosted_domain) ⇒ Boolean
- .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 90 91 92 93 94 |
# 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: nil ) if nonce token_nonce = profile&.fetch("nonce", nil) expected_nonces = [nonce, OpenSSL::Digest::SHA256.hexdigest(nonce.to_s)] return false unless token_nonce.is_a?(String) && expected_nonces.include?(token_nonce) end !!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 31 |
# 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, require_code_verifier: 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 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 |
# File 'lib/better_auth/social_providers/cognito.rb', line 7 def cognito(client_id:, client_secret: nil, scopes: ["openid", "profile", "email"], **) normalized = Base.() domain = normalized[:domain] region = normalized[:region] user_pool_id = normalized[:user_pool_id] raise Error, "DOMAIN_AND_REGION_REQUIRED" if domain.to_s.empty? || region.to_s.empty? || user_pool_id.to_s.empty? if normalized[:require_client_secret] && client_secret.to_s.empty? raise Error, "CLIENT_SECRET_REQUIRED" end clean_domain = domain.to_s.sub(%r{\Ahttps?://}, "").sub(%r{/+\z}, "") provider = Base.oauth_provider( id: "cognito", name: "Cognito", client_id: client_id, client_secret: client_secret, authorization_endpoint: "https://#{clean_domain}/oauth2/authorize", token_endpoint: "https://#{clean_domain}/oauth2/token", user_info_endpoint: "https://#{clean_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"] } }, ** ) = provider.fetch(:create_authorization_url) provider[:create_authorization_url] = lambda do |data| .call(data).sub(/scope=([^&]+)/) { "scope=#{$1.gsub("+", "%20")}" } end provider[:verify_id_token] ||= lambda do |token, nonce = nil| return false if normalized[:disable_id_token_sign_in] profile = Base.verify_jwt_with_jwks( token, jwks: normalized[:jwks], jwks_endpoint: normalized[:jwks_endpoint] || "https://cognito-idp.#{region}.amazonaws.com/#{user_pool_id}/.well-known/jwks.json", algorithms: ["RS256"], issuers: "https://cognito-idp.#{region}.amazonaws.com/#{user_pool_id}", audience: Array(client_id), nonce: nonce, max_age: 3600 ) !!profile&.fetch("sub", nil) end provider[:get_user_info] = lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom if Base.id_token(tokens) profile = Base.decode_jwt_payload(Base.id_token(tokens)) unless profile.empty? name = profile["name"] || profile["given_name"] || profile["username"] || "" enriched = profile.merge("name" => name) user = Base.apply_profile_mapping( { id: profile["sub"], name: name, email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] }, enriched, normalized ) next({user: user, data: enriched}) end end profile = Base.fetch_user_info("https://#{clean_domain}/oauth2/userinfo", tokens) next nil unless profile user = Base.apply_profile_mapping( { id: profile["sub"], name: profile["name"] || profile["given_name"] || profile["username"] || "", email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] }, profile, normalized ) {user: user, data: profile} end provider 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 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 |
# File 'lib/better_auth/social_providers/facebook.rb', line 7 def facebook(client_id:, client_secret:, scopes: ["email", "public_profile"], **) normalized = Base.() 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"] } }, ** ) verify_access_token = lambda do |access_token| primary_client_id = Base.primary_client_id(client_id) next nil if access_token.to_s.empty? || client_secret.to_s.empty? response = Base.get_json(Base.("https://graph.facebook.com/debug_token", { input_token: access_token, access_token: "#{primary_client_id}|#{client_secret}" })) data = response.is_a?(Hash) ? response["data"] : nil next nil unless data.is_a?(Hash) next nil unless data["is_valid"] == true next nil unless Array(client_id).map(&:to_s).include?(data["app_id"].to_s) next nil if data["app_id"].to_s.empty? || data["user_id"].to_s.empty? data["user_id"] rescue nil end provider[:verify_id_token] = lambda do |token, nonce = nil| return false if normalized[:disable_id_token_sign_in] custom = normalized[:verify_id_token] next custom.call(token, nonce) if custom begin unless token.to_s.split(".").length == 3 next !!verify_access_token.call(token) end profile = Base.verify_jwt_with_jwks( token, jwks: normalized[:jwks], jwks_endpoint: normalized[:jwks_endpoint] || "https://limited.facebook.com/.well-known/oauth/openid/jwks/", algorithms: ["RS256"], issuers: "https://www.facebook.com", audience: Array(client_id), nonce: nonce ) !!profile rescue false end end provider[:get_user_info] = lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom begin token = Base.id_token(tokens) if token && token.split(".").length == 3 profile = Base.decode_jwt_payload(token) next nil if profile["sub"].to_s.empty? user = Base.apply_profile_mapping( { id: profile["sub"], name: profile["name"], email: profile["email"], image: profile["picture"], emailVerified: false }, profile.merge("email_verified" => false), normalized ) next({user: user, data: profile}) end access_token = Base.access_token(tokens) next nil if access_token.to_s.empty? token_user_id = verify_access_token.call(access_token) next nil unless token_user_id profile = Base.fetch_user_info("https://graph.facebook.com/me?fields=#{URI.encode_www_form_component(fields)}", tokens) next nil unless profile.is_a?(Hash) next nil unless profile["id"] == token_user_id picture = profile.dig("picture", "data", "url") || profile["picture"] user = Base.apply_profile_mapping( { id: profile["id"] || profile["sub"], name: profile["name"], email: profile["email"], image: picture, emailVerified: !!profile["email_verified"] }, profile, normalized ) {user: user, data: profile} rescue nil end end 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 30 31 |
# 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, require_code_verifier: true, token_authentication: :basic, 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 73 74 |
# 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) next nil unless profile 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
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 |
# File 'lib/better_auth/social_providers/google.rb', line 15 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, options: normalized, 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) && google_hosted_domain_allowed?(normalized[:hd], profile["hd"]) 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)) next nil unless google_hosted_domain_allowed?(normalized[:hd], profile["hd"]) 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 |
.google_hosted_domain_allowed?(configured_hosted_domain, token_hosted_domain) ⇒ Boolean
7 8 9 10 11 12 13 |
# File 'lib/better_auth/social_providers/google.rb', line 7 def google_hosted_domain_allowed?(configured_hosted_domain, token_hosted_domain) return true if configured_hosted_domain.nil? || configured_hosted_domain.to_s.empty? return false unless token_hosted_domain.is_a?(String) && !token_hosted_domain.strip.empty? return true if configured_hosted_domain == "*" token_hosted_domain == configured_hosted_domain 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 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/line.rb', line 7 def line(client_id:, client_secret:, scopes: ["openid", "profile", "email"], **) normalized = Base.() 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] ||= lambda do |token, nonce = nil| return false if normalized[:disable_id_token_sign_in] profile = Base.post_form_json( normalized[:verify_id_token_endpoint] || "https://api.line.me/oauth2/v2.1/verify", {id_token: token, client_id: client_id, nonce: nonce} ) next false unless profile next false unless profile["aud"] == client_id next false if profile["nonce"] && profile["nonce"] != nonce true end provider[: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.fetch_user_info("https://api.line.me/oauth2/v2.1/userinfo", tokens) if profile.empty? next nil unless profile && !profile.empty? user = Base.apply_profile_mapping( { id: profile["sub"] || profile["userId"], name: profile["name"] || profile["displayName"] || "", email: profile["email"], image: profile["picture"] || profile["pictureUrl"], emailVerified: false }, profile, normalized ) {user: user, data: profile} end 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 43 |
# 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) return nil unless 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
23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/better_auth/social_providers/microsoft_entra_id.rb', line 23 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
139 140 141 142 143 144 |
# File 'lib/better_auth/social_providers/microsoft_entra_id.rb', line 139 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
10 11 12 13 14 15 16 17 18 19 20 21 |
# File 'lib/better_auth/social_providers/microsoft_entra_id.rb', line 10 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
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 129 130 131 132 133 134 135 136 137 |
# File 'lib/better_auth/social_providers/microsoft_entra_id.rb', line 36 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 ) tid = profile&.fetch("tid", nil) next false unless tid.is_a?(String) next false unless profile["iss"] == "#{.to_s.sub(%r{/+\z}, "")}/#{tid}/v2.0" next false if tenant_id.to_s == "organizations" && tid == MICROSOFT_CONSUMER_TENANT_ID next false if tenant_id.to_s == "consumers" && tid != MICROSOFT_CONSUMER_TENANT_ID !!(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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/better_auth/social_providers/naver.rb', line 7 def naver(client_id:, client_secret:, scopes: ["profile", "email"], **) normalized = Base.() provider = 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 } }, ** ) provider[:get_user_info] = lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom profile = Base.fetch_user_info("https://openapi.naver.com/v1/nid/me", tokens) next nil unless profile && profile["resultcode"] == "00" data = profile["response"] || {} user = Base.apply_profile_mapping( { id: data["id"], name: data["name"] || data["nickname"] || "", email: data["email"], image: data["profile_image"], emailVerified: false }, profile, normalized ) {user: user, data: profile} end provider 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 32 |
# 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"}, token_authentication: :basic, 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/better_auth/social_providers/paybin.rb', line 7 def paybin(client_id:, client_secret:, scopes: ["openid", "email", "profile"], **) normalized = Base.() issuer = ([:issuer] || "https://idp.paybin.io").to_s.sub(%r{/+\z}, "") provider = 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, require_code_verifier: true, profile_map: ->(profile) { { id: profile["sub"], name: profile["name"] || profile["preferred_username"] || "", email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] } }, ** ) provider[: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)) next nil if profile.empty? user = Base.apply_profile_mapping( { id: profile["sub"], name: profile["name"] || profile["preferred_username"] || "", email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] }, profile, normalized ) {user: user, data: profile} end provider 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 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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/better_auth/social_providers/paypal.rb', line 7 def paypal(client_id:, client_secret:, scopes: [], **) normalized = Base.() 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" token_endpoint = normalized[:token_endpoint] || "#{api_host}/v1/oauth2/token" issuer = sandbox ? "https://www.sandbox.paypal.com" : "https://www.paypal.com" jwks_endpoint = normalized[:jwks_endpoint] || (sandbox ? "https://api.sandbox.paypal.com/v1/oauth2/certs" : "https://api.paypal.com/v1/oauth2/certs") provider = Base.oauth_provider( id: "paypal", name: "PayPal", client_id: client_id, client_secret: client_secret, authorization_endpoint: "#{auth_host}/signin/authorize", token_endpoint: token_endpoint, user_info_endpoint: "#{api_host}/v1/identity/oauth2/userinfo?schema=paypalv1.1", scopes: scopes, pkce: true, token_authentication: :basic, profile_map: ->(profile) { { id: profile["user_id"], name: profile["name"], email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] } }, ** ) provider[:validate_authorization_code] = lambda do |data| Base.normalize_tokens(Base.post_token_form( token_endpoint, { code: data[:code] || data["code"], grant_type: "authorization_code", redirect_uri: data[:redirect_uri] || data[:redirectURI] || data["redirect_uri"] || data["redirectURI"] }, client_id: Base.primary_client_id(client_id), client_secret: client_secret, authentication: :basic, headers: {"Accept-Language" => "en_US"} )) end provider[:refresh_access_token] = lambda do |refresh_token| Base.normalize_tokens(Base.post_token_form( token_endpoint, { grant_type: "refresh_token", refresh_token: refresh_token }, client_id: Base.primary_client_id(client_id), client_secret: client_secret, authentication: :basic, headers: {"Accept-Language" => "en_US"} )) end provider[:verify_id_token] = lambda do |token, nonce = nil| return false if normalized[:disable_id_token_sign_in] custom = normalized[:verify_id_token] next custom.call(token, nonce) if custom begin encoded_header, _encoded_payload, signature = token.to_s.split(".", 3) next false if encoded_header.to_s.empty? || signature.to_s.empty? header = JSON.parse(Base64.urlsafe_decode64(Base.padded_base64(encoded_header))) algorithm = header["alg"] profile = case algorithm when "RS256" Base.verify_jwt_with_jwks( token, jwks: normalized[:jwks], jwks_endpoint: jwks_endpoint, algorithms: ["RS256"], issuers: issuer, audience: Array(client_id), nonce: nonce, max_age: 3600 ) when "HS256" next false if client_secret.to_s.empty? claims, = JWT.decode(token.to_s, client_secret, true, { algorithm: "HS256", aud: Array(client_id), verify_aud: true, iss: issuer, verify_iss: true }) next false if nonce && claims["nonce"] != nonce claims else next false end issued_at = Integer(profile.fetch("iat")) token_age = Time.now.to_i - issued_at next false if token_age.negative? || token_age > 3600 !!profile.fetch("sub", nil) rescue false end end provider[:get_user_info] = lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom begin access_token = Base.access_token(tokens) next nil if access_token.to_s.empty? profile = Base.fetch_user_info("#{api_host}/v1/identity/oauth2/userinfo?schema=paypalv1.1", tokens) next nil unless profile.is_a?(Hash) next nil if profile["user_id"].to_s.empty? id_token = Base.id_token(tokens) if id_token id_token_subject = Base.decode_jwt_payload(id_token)["sub"] profile_subject = profile["sub"] || profile["user_id"] next nil if id_token_subject.to_s.empty? || profile_subject.to_s.empty? || id_token_subject != profile_subject end user = Base.apply_profile_mapping( { id: profile["user_id"], name: profile["name"], email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] }, profile, normalized ) {user: user, data: profile} rescue nil end end 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/better_auth/social_providers/reddit.rb', line 7 def reddit(client_id:, client_secret:, scopes: ["identity"], **) normalized = Base.() token_endpoint = normalized[:token_endpoint] || "https://www.reddit.com/api/v1/access_token" provider = 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: token_endpoint, 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]} }, token_authentication: :basic, profile_map: ->(profile) { { id: profile["id"], name: profile["name"], email: "#{profile["id"]}@reddit.invalid", image: profile["icon_img"].to_s.split("?").first, emailVerified: false } }, ** ) provider[:validate_authorization_code] = lambda do |data| Base.post_token_form( token_endpoint, { code: data[:code] || data["code"], grant_type: "authorization_code", redirect_uri: data[:redirect_uri] || data[:redirectURI] || data["redirect_uri"] || data["redirectURI"] }, client_id: Base.primary_client_id(client_id), client_secret: client_secret, authentication: :basic, headers: { "accept" => "text/plain", "user-agent" => "better-auth" } ) end provider 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 37 |
# 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, require_code_verifier: 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/better_auth/social_providers/twitch.rb', line 7 def twitch(client_id:, client_secret:, scopes: ["user:read:email", "openid"], **) normalized = Base.() provider = 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"] } }, ** ) provider[: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)) next nil if profile.empty? user = Base.apply_profile_mapping( { id: profile["sub"], name: profile["preferred_username"], email: profile["email"], image: profile["picture"], emailVerified: !!profile["email_verified"] }, profile, normalized ) {user: user, data: profile} end provider 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 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/twitter.rb', line 7 def twitter(client_id:, client_secret:, scopes: ["users.read", "tweet.read", "offline.access", "users.email"], **) normalized = Base.() provider = 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, token_authentication: :basic, 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"] } }, ** ) provider[:get_user_info] = lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom profile = Base.get_json( "https://api.x.com/2/users/me?user.fields=profile_image_url", "Authorization" => "Bearer #{Base.access_token(tokens)}" ) next nil unless profile email_data = Base.get_json( "https://api.x.com/2/users/me?user.fields=confirmed_email", "Authorization" => "Bearer #{Base.access_token(tokens)}" ) data = profile["data"] || profile confirmed_email = email_data&.dig("data", "confirmed_email") data["email"] = confirmed_email if confirmed_email data["confirmed_email"] = true if confirmed_email user = Base.apply_profile_mapping( { id: data["id"], name: data["name"], email: data["email"] || data["username"], image: data["profile_image_url"], emailVerified: !!data["confirmed_email"] }, profile, normalized ) {user: user, data: profile} end provider 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 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/vk.rb', line 7 def vk(client_id:, client_secret:, scopes: ["email", "phone"], **) normalized = Base.() provider = 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 } }, ** ) provider[:get_user_info] = lambda do |tokens| custom = normalized[:get_user_info] next custom.call(tokens) if custom access_token = Base.access_token(tokens) next nil unless access_token profile = Base.post_json( "https://id.vk.com/oauth2/user_info", {access_token: access_token, client_id: client_id}, "Content-Type" => "application/x-www-form-urlencoded" ) next nil unless profile user_profile = profile["user"] || profile user_map = normalized[:map_profile_to_user]&.call(profile) || {} next nil if user_profile["email"].to_s.empty? && user_map[:email].to_s.empty? && user_map["email"].to_s.empty? user = { id: user_profile["user_id"], first_name: user_profile["first_name"], last_name: user_profile["last_name"], name: [user_profile["first_name"], user_profile["last_name"]].compact.join(" "), email: user_profile["email"], image: user_profile["avatar"], emailVerified: false, birthday: user_profile["birthday"], sex: user_profile["sex"] }.merge(user_map) {user: user, data: profile} end provider 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 103 104 |
# 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"] { id: id, name: profile["nickname"], email: profile["email"] || "#{id}@wechat.invalid", 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"] id = profile["unionid"] || profile["openid"] || openid user = Base.apply_profile_mapping( { id: id, name: profile["nickname"], email: profile["email"] || "#{id}@wechat.invalid", 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 |