validateRedirectURIs validates redirect URIs according to RFC 7591, 8252
(uris []string, tokenEndpointAuthMethod OAuth2TokenEndpointAuthMethod)
| 120 | |
| 121 | // validateRedirectURIs validates redirect URIs according to RFC 7591, 8252 |
| 122 | func validateRedirectURIs(uris []string, tokenEndpointAuthMethod OAuth2TokenEndpointAuthMethod) error { |
| 123 | if len(uris) == 0 { |
| 124 | return xerrors.New("at least one redirect URI is required") |
| 125 | } |
| 126 | |
| 127 | for i, uriStr := range uris { |
| 128 | if uriStr == "" { |
| 129 | return xerrors.Errorf("redirect URI at index %d cannot be empty", i) |
| 130 | } |
| 131 | |
| 132 | uri, err := url.Parse(uriStr) |
| 133 | if err != nil { |
| 134 | return xerrors.Errorf("redirect URI at index %d is not a valid URL: %w", i, err) |
| 135 | } |
| 136 | |
| 137 | if err := validateScheme(uri); err != nil { |
| 138 | return xerrors.Errorf("redirect URI at index %d: %w", i, err) |
| 139 | } |
| 140 | |
| 141 | // The urn:ietf:wg:oauth:2.0:oob scheme passed validation |
| 142 | // above but needs no further checks. |
| 143 | if uri.Scheme == "urn" { |
| 144 | continue |
| 145 | } |
| 146 | |
| 147 | // Determine if this is a public client based on token endpoint auth method |
| 148 | isPublicClient := tokenEndpointAuthMethod == OAuth2TokenEndpointAuthMethodNone |
| 149 | |
| 150 | // Handle different validation for public vs confidential clients |
| 151 | if uri.Scheme == "http" || uri.Scheme == "https" { |
| 152 | // HTTP/HTTPS validation (RFC 8252 section 7.3) |
| 153 | if uri.Scheme == "http" { |
| 154 | if isPublicClient { |
| 155 | // For public clients, only allow loopback (RFC 8252) |
| 156 | if !isLoopbackAddress(uri.Hostname()) { |
| 157 | return xerrors.Errorf("redirect URI at index %d: public clients may only use http with loopback addresses (127.0.0.1, ::1, localhost)", i) |
| 158 | } |
| 159 | } else { |
| 160 | // For confidential clients, allow localhost for development |
| 161 | if !isLocalhost(uri.Hostname()) { |
| 162 | return xerrors.Errorf("redirect URI at index %d must use https scheme for non-localhost URLs", i) |
| 163 | } |
| 164 | } |
| 165 | } |
| 166 | } else { |
| 167 | // Custom scheme validation for public clients (RFC 8252 section 7.1) |
| 168 | if isPublicClient { |
| 169 | // For public clients, custom schemes should follow RFC 8252 recommendations |
| 170 | // Should be reverse domain notation based on domain under their control |
| 171 | if !isValidCustomScheme(uri.Scheme) { |
| 172 | return xerrors.Errorf("redirect URI at index %d: custom scheme %s should use reverse domain notation (e.g. com.example.app)", i, uri.Scheme) |
| 173 | } |
| 174 | } |
| 175 | // For confidential clients, custom schemes are less common but allowed |
| 176 | } |
| 177 | |
| 178 | // Prevent URI fragments (RFC 6749 section 3.1.2) |
| 179 | if uri.Fragment != "" || strings.Contains(uriStr, "#") { |
no test coverage detected