IsValidRedirectURI validates a redirect URI for security-critical flows (password reset, magic link, OAuth, etc.). Unlike IsValidOrigin (used for CORS), this function never accepts "*" as a blanket pass. When allowed_origins contains only "*" (the default), it restricts redirects to the server's own
(redirectURI string, allowedOrigins []string, hostname string)
| 32 | // it restricts redirects to the server's own hostname. When explicit origins are |
| 33 | // configured, it validates against those using the same matching logic as IsValidOrigin. |
| 34 | func IsValidRedirectURI(redirectURI string, allowedOrigins []string, hostname string) bool { |
| 35 | u, err := url.Parse(redirectURI) |
| 36 | if err != nil { |
| 37 | return false |
| 38 | } |
| 39 | // Only allow http and https schemes to prevent javascript:, data:, ftp:, etc. |
| 40 | if u.Scheme != "http" && u.Scheme != "https" { |
| 41 | return false |
| 42 | } |
| 43 | |
| 44 | origins := allowedOrigins |
| 45 | if len(origins) == 0 { |
| 46 | origins = []string{"*"} |
| 47 | } |
| 48 | |
| 49 | redirectOrigin := normalizeOrigin(redirectURI) |
| 50 | |
| 51 | // When allowed_origins is wildcard, only allow redirects to the server's own hostname |
| 52 | if len(origins) == 1 && origins[0] == "*" { |
| 53 | return redirectOrigin == normalizeOrigin(hostname) |
| 54 | } |
| 55 | |
| 56 | // Validate against explicit allowed origins (same logic as IsValidOrigin) |
| 57 | for _, origin := range origins { |
| 58 | pattern := normalizeOrigin(origin) |
| 59 | |
| 60 | if strings.Contains(origin, "*") { |
| 61 | pattern = strings.ReplaceAll(pattern, ".", "\\.") |
| 62 | // Subdomain wildcard: *.example.com must only match |
| 63 | // proper subdomains (sub.example.com), not evil-example.com |
| 64 | // or the bare domain (example.com). |
| 65 | if strings.HasPrefix(pattern, "*\\.") { |
| 66 | // Replace leading *\. with one or more dot-terminated DNS labels, |
| 67 | // ensuring a proper dot boundary before the base domain. |
| 68 | pattern = "([^.]+\\.)+" + pattern[3:] |
| 69 | } else { |
| 70 | pattern = strings.ReplaceAll(pattern, "*", "[^.]*") |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | if matched, _ := regexp.MatchString("^"+pattern+"$", redirectOrigin); matched { |
| 75 | return true |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | return false |
| 80 | } |
| 81 | |
| 82 | // IsValidOrigin validates origin based on ALLOWED_ORIGINS |
| 83 | func IsValidOrigin(inputURL string, allowedOriginsConfig []string) bool { |