OAuth redirect URI and scope checker
Save on GoogleFree OAuth redirect URI and scope checker. Compare your request with the registered URIs, check scopes, and get cleaned parameters. Everything runs in your browser. Nothing is uploaded.
Configure OAuth request
Results & Output
Redirect URIs
Scopes
The checker runs in your browser. Do not paste authorization codes or tokens.
Passkeys, OAuth and Identity
| Tool | What it does |
|---|---|
| WebAuthn options JSON builder / inspector | Build WebAuthn registration or sign-in options, or paste existing JSON to check it. It catches mistakes such as a padded challenge, a wrong rp.id, or an email used as the user ID, so your passkey setup works the first time. |
| PKCE code verifier / challenge generator | Create a PKCE code verifier and its S256 code challenge in your browser, check values you already have, and get the authorization and token requests ready to use. Handy when a login fails with invalid_grant. |
| OIDC discovery document validator | Paste the JSON from /.well-known/openid-configuration and see which required fields are missing, which URLs are wrong, and whether the issuer matches. The results are color-coded, so you spot problems fast. |
| JWKS viewer / JWK ↔ PEM converter | View the keys inside a JWKS, check their size and curve, and convert between JWK and PEM. It flags private key data, so you never publish it by mistake. |
What does an OAuth redirect URI and scope checker do?
Most OAuth sign-in errors come from two small things: a redirect URI that is not an exact match, and a scope string in the wrong format. This checker compares the redirect URI in your request with the URIs registered for your app, flags unsafe or invalid URIs (wildcards, fragments, plain http, risky custom schemes), and checks the scopes for commas, case mistakes, duplicates, broad permissions and a missing openid. Results are color-coded, and the output gives you the cleaned, URL-encoded parameters. Providers differ in details, so check your provider’s documentation.
Technical OAuth redirect URI and scope references
Click Copy to copy a snippet. Snippets are examples, so replace placeholders such as app.example.com and auth.example.com with your own values. Providers differ, so check your provider’s documentation.
| Developer Question | Copy-ready Snippet | Explanation |
|---|---|---|
| RuleWhat is a redirect URI? | redirect_uri=https://app.example.com/callback | The URL where the authorization server sends the user back, with the authorization code, after sign-in. |
| RuleRedirect URI vs callback URL vs reply URL | https://app.example.com/callback | They are the same thing. Different providers use different names, such as callback URL or reply URL. |
| RuleDo I have to register the redirect URI? | // register every redirect URI in the provider’s app settings | Yes. Authorization servers only redirect to URIs that were registered for the client. |
| RuleMust I send redirect_uri in the request? | redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback | If only one is registered some servers let you omit it, but sending it is safer and often required. |
| RuleExact string matching for redirect URIs | https://app.example.com/callback != https://app.example.com/callback/ | The OAuth security best current practice (RFC 9700) says servers must compare redirect URIs as exact strings. |
| SecurityCan a redirect URI contain a wildcard? | https://*.example.com/callback | Avoid it. Wildcards let attackers use another host or path to steal codes. Register exact URIs. |
| RuleCan a redirect URI contain a fragment? | https://app.example.com/callback#done | No. RFC 6749 forbids a fragment (#) in the redirect URI. |
| URICan a redirect URI have a query string? | https://app.example.com/callback?tenant=a | RFC 6749 allows it, and the server must keep it. Many providers reject or ignore it, so check yours. |
| Securityhttp vs https redirect URIs | https://app.example.com/callback | Use https. Plain http is only acceptable for loopback addresses during development or for desktop apps. |
| ErrorTrailing slash in a redirect URI | "https://app.example.com/callback" vs "https://app.example.com/callback/" | A trailing slash makes the string different. Register and send exactly the same form. |
| RuleAre redirect URIs case-sensitive? | https://App.Example.com/Callback | Scheme and host are case-insensitive, but the path is case-sensitive. Providers usually compare the whole string exactly, so use lowercase. |
| URIPort numbers in a redirect URI | https://app.example.com:8443/callback | A port is part of the string. The registered and sent values must both include it. |
| URIlocalhost for development | http://localhost:3000/callback | Most providers allow http for localhost during development. Never register it in production. |
| URI127.0.0.1 vs localhost | http://127.0.0.1:8080/callback | RFC 8252 prefers the IP literal over localhost for loopback redirects. |
| URIIPv6 loopback redirect URI | http://[::1]:8080/callback | RFC 8252 allows the IPv6 loopback address too. |
| URILoopback redirect with a random port | http://127.0.0.1/callback | Desktop apps often listen on a free port. RFC 8252 lets servers accept any port for loopback URIs, but support depends on the provider. |
| URICustom URI scheme for a mobile app | com.example.app:/oauth2redirect | A private-use scheme that opens your app. Use a reverse domain name that you control. |
| SecurityWhy use a reverse domain name as the scheme? | com.example.app:/callback | It makes a clash with another app unlikely. A short scheme such as myapp: is easy for another app to claim. |
| URISingle slash in a custom scheme URI | com.example.app:/callback | RFC 8252 recommends one slash after the colon because the part after it is a path, not a host. |
| URIApp-claimed https links for mobile | https://app.example.com/oauth/callback | Android App Links and iOS Universal Links open your app for an https URL. They are safer than custom schemes. |
| How-toAndroid intent filter for a custom scheme | <intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.example.app" android:host="oauth2redirect" />
</intent-filter> | Add it to the activity that handles the redirect. The scheme and host must match the redirect URI. |
| How-toiOS URL scheme in Info.plist | <key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array><string>com.example.app</string></array>
</dict>
</array> | This registers the scheme so iOS opens your app for com.example.app: URLs. |
| How-toAndroid App Links file location | https://app.example.com/.well-known/assetlinks.json | Host this file to prove that your app owns the https redirect URI. |
| How-toiOS Universal Links file location | https://app.example.com/.well-known/apple-app-site-association | Host this file so iOS opens your app for the https redirect URI. |
| How-toRedirect URI for a single-page app | https://app.example.com/callback | A page in your app that reads the code and state from the URL and exchanges the code. |
| How-toRedirect URI for a server-side web app | https://app.example.com/auth/callback | A server route that receives the code. Confidential clients also authenticate at the token endpoint. |
| URIRedirect URI for desktop apps | http://127.0.0.1:53682/callback | A desktop app can listen on a loopback port and receive the redirect in the system browser. |
| SecurityDo not use an embedded web view | // open the system browser for sign-in | RFC 8252 recommends the system browser for native apps, because an embedded view can read the user’s credentials. |
| How-toRegister one redirect URI per environment | https://dev.example.com/callback
https://staging.example.com/callback
https://app.example.com/callback | List each exact URI for development, staging and production. Do not use a wildcard. |
| How-toRedirect URIs for many tenants or subdomains | https://tenant1.example.com/callback
https://tenant2.example.com/callback | Register each URI. If you need many, use a single callback host and carry the tenant in state. |
| How-toCarry a return path without changing the redirect URI | sessionStorage.setItem("returnTo", "/dashboard"); | Keep the redirect URI fixed. Store the page to return to in the session and read it after sign-in. |
| SecurityOpen redirect risk in a redirect URI | https://app.example.com/callback?next=https://evil.example | A redirect URI that forwards to another URL can send the code to an attacker. Avoid such parameters. |
| SecurityWhy exact matching protects the code | // attacker URI is not on the allow-list, so the server refuses it | If the server accepted a partial match, an attacker could receive the authorization code at their own URL. |
| How-toURL-encode the redirect_uri | encodeURIComponent("https://app.example.com/callback") | Encode it when you put it in the query string of the authorization request. |
| How-toBuild the authorization URL in JavaScript | const url = new URL("https://auth.example.com/authorize");
url.search = new URLSearchParams({ response_type: "code", client_id, redirect_uri, scope, state }); | URLSearchParams encodes the redirect URI and the scope for you. |
| How-toRead the code from the callback | const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const state = params.get("state"); | Check state before you exchange the code. |
| How-toHandle an error callback | const error = params.get("error");
const description = params.get("error_description"); | Failures come back as error and error_description in the same query string. |
| How-toDebug a redirect URI mismatch | console.log(JSON.stringify(requestUri), JSON.stringify(registeredUri)); | JSON.stringify shows hidden spaces and line breaks that hide a mismatch. |
| ErrorWhitespace or newline in an environment variable | const redirectUri = process.env.REDIRECT_URI.trim(); | A trailing newline in a config value is a common cause of a mismatch. |
| ErrorReverse proxy changes https to http | proxy_set_header X-Forwarded-Proto $scheme; | Behind a proxy your app may build an http redirect URI. Forward the scheme and make the framework trust it. |
| ErrorWrong host behind a proxy | proxy_set_header Host $host; | Pass the original host so the app builds the public redirect URI, not an internal one. |
| Errorredirect_uri_mismatch (Google) | Error 400: redirect_uri_mismatch | The redirect URI in the request is not in the OAuth client’s authorized redirect URIs. |
| ErrorAADSTS50011 (Microsoft) | AADSTS50011: The redirect URI specified in the request does not match the redirect URIs configured for the application | Add the exact URI under the app registration’s redirect URIs. |
| ErrorCallback URL mismatch (Auth0) | Callback URL mismatch | The URL must be in the Allowed Callback URLs list of the Auth0 application. |
| ErrorInvalid parameter: redirect_uri (Keycloak) | Invalid parameter: redirect_uri | Add the URI to the client’s Valid redirect URIs. Keycloak allows wildcards in that field, so avoid broad patterns. |
| Errorredirect_mismatch (Cognito) | error=redirect_mismatch | The redirect URI must match one of the app client’s callback URLs exactly. |
| ErrorGitHub redirect_uri error | The redirect_uri MUST match the registered callback URL for this application. | GitHub compares the host and port exactly, and allows a deeper path under the registered callback URL. |
| How-toPassport.js callbackURL | callbackURL: "https://app.example.com/auth/google/callback" | Set the full URL, and register the same string with the provider. |
| How-toSpring Security default redirect URI | {baseUrl}/login/oauth2/code/{registrationId} | Spring builds this URI for each client registration. Register the expanded value with the provider. |
| How-toASP.NET Core OpenID Connect callback | options.CallbackPath = "/signin-oidc"; | This is the default path. The registered URI is your site’s base URL plus this path. |
| How-toNextAuth.js callback URL | https://app.example.com/api/auth/callback/google | The path ends with the provider ID. Register it exactly. |
| How-toDjango allauth callback URL | https://app.example.com/accounts/google/login/callback/ | Note the trailing slash. Register it exactly as shown. |
| How-toDevise OmniAuth callback URL | https://app.example.com/users/auth/google_oauth2/callback | The path follows the model name and provider name. |
| How-toLaravel Socialite redirect | 'redirect' => env('GOOGLE_REDIRECT_URI'), | Keep the value in the environment and make sure it matches the provider setting. |
| How-toAuthlib redirect URI in Flask | redirect_uri = url_for("auth", _external=True) | _external=True builds a full URL. Check that the scheme and host are correct behind a proxy. |
| SecurityDo not log the callback URL | // the callback URL contains the authorization code | Codes and tokens in URLs can leak through logs and analytics. Redact them. |
| SecurityRemove unused redirect URIs | // review the list of registered URIs every few months | Old URIs, especially on abandoned domains, give attackers a way in. |
| SecuritySubdomain takeover and redirect URIs | https://old.example.com/callback | A redirect URI on a subdomain that you no longer control can be claimed by an attacker. |
| ScopeWhat is a scope? | scope=openid profile email | A scope names a permission the app asks for. The user sees the requested scopes on the consent screen. |
| ScopeScopes are separated by spaces | scope=read write | RFC 6749 defines a space-delimited list. Commas are wrong for most providers. |
| ScopeProviders that accept commas | scope=public_profile,email | Some providers, such as Facebook, use commas. The standard format is a space-separated list. |
| How-toEncode the scope parameter | scope=openid%20profile%20email | Use %20 for each space (or +) in the query string. encodeURIComponent does this. |
| ScopeAre scopes case-sensitive? | scope=openid // not OpenID | Yes. "OpenID" is not the same as "openid". |
| ScopeWhat does the openid scope do? | scope=openid | It turns an OAuth request into an OpenID Connect request and returns an ID token. |
| ScopeWhat does the profile scope return? | scope=openid profile | Basic profile claims such as name, family_name, given_name and picture. |
| ScopeWhat does the email scope return? | scope=openid email | The email and email_verified claims. |
| ScopeWhat do address and phone return? | scope=openid address phone | The postal address claim and the phone number claims. |
| ScopeWhat does offline_access do? | scope=openid offline_access | It asks for a refresh token so the app can get new access tokens without the user. Providers may need extra settings. |
| ScopeMinimum scopes for an OpenID Connect login | scope=openid profile email | This is the common set for sign-in. Remove profile or email if you do not need them. |
| SecurityRequest the least privilege | scope=read:orders | Ask only for what the feature needs. Broad scopes such as admin or * create risk and scare users. |
| ScopeGranted scopes can differ from requested scopes | const granted = tokenResponse.scope.split(" "); | The user or the server can grant fewer scopes. Always read the scope in the token response. |
| ScopeScope in a token response | { "access_token": "...", "token_type": "Bearer", "scope": "openid profile" } | The scope field lists what was actually granted. |
| How-toRead scopes from an access token | const scopes = (payload.scope ?? "").split(" "); | Many providers put scopes in the scope claim. Some use scp, which can be an array. |
| How-toCheck a required scope in an API | if (!scopes.includes("read:orders")) return res.sendStatus(403); | Resource servers should reject tokens that lack the needed scope. |
| ScopeScope naming styles | read:orders orders.read https://api.example.com/orders.read | Providers use resource:action, dotted names or URLs. Follow your API’s convention. |
| ScopeGoogle scopes are URLs | https://www.googleapis.com/auth/userinfo.email | Google uses URL-style scopes for its APIs, plus the standard openid, profile and email. |
| ScopeMicrosoft Graph scopes | scope=User.Read offline_access | Microsoft Graph delegated permissions look like User.Read. |
| ScopeMicrosoft .default scope | scope=https://graph.microsoft.com/.default | .default asks for the permissions already configured on the app registration. |
| ScopeGitHub scopes | scope=read:user user:email | GitHub uses names such as read:user, user:email and repo. |
| ScopeIncremental authorization | include_granted_scopes=true | Some providers, such as Google, let you ask for more scopes later and combine them with earlier grants. |
| Errorinvalid_scope error | error=invalid_scope | The scope is unknown, malformed, or not allowed for this client. Compare it with the provider’s list. |
| ErrorAADSTS70011 invalid scope | AADSTS70011: The provided value for the input parameter 'scope' is not valid. | Check the scope names and spacing for the Microsoft identity platform. |
| How-toSpaces in a scope with curl | curl -X POST https://auth.example.com/oauth/token --data-urlencode "scope=openid profile" | --data-urlencode encodes the spaces for you. |
| How-toScope in the client credentials grant | grant_type=client_credentials&scope=api.read | Machine-to-machine tokens still use scopes to limit access. |
| ScopeScope vs audience | audience=https://api.example.com | A scope says what the app may do. An audience says which API the token is for. Some providers need both. |
| ScopeClaims returned for each scope | profile -> name, picture email -> email, email_verified | The scopes you request decide which claims the ID token or the userinfo endpoint can return. |
| How-toWhich scopes does a provider support? | curl -s https://auth.example.com/.well-known/openid-configuration | jq .scopes_supported | Read scopes_supported from the discovery document. |
| How-toClean a scope string in JavaScript | const scope = [...new Set(input.split(/[\s,]+/).filter(Boolean))].join(" "); | Splits on spaces and commas, drops empty parts and removes duplicates. |
| How-toValidate a scope name with a regular expression | /^[\x21\x23-\x5B\x5D-\x7E]+$/.test(scope) | This is the RFC 6749 rule: printable ASCII without spaces, double quotes or backslashes. |
| SecurityToo many scopes on the consent screen | scope=openid profile | A long list of permissions makes users hesitate. Request extra scopes only when a feature needs them. |
| SecurityScope for refresh tokens in a single-page app | scope=openid offline_access | Keeping refresh tokens in a browser is risky. Prefer rotation, short lifetimes, or a backend. |
| SecurityAdd state to the request | const state = base64url(crypto.getRandomValues(new Uint8Array(16))); | Check state on the callback to stop forged responses. |
| SecurityUse PKCE with every redirect | code_challenge_method=S256 | PKCE stops an attacker who steals the code from using it. See the PKCE generator. |
| SecurityAuthorization code interception on mobile | // another app registers the same custom scheme | A malicious app can capture the code when schemes clash. PKCE and claimed https links reduce this risk. |
| SecurityToken in the URL fragment | https://app.example.com/callback#access_token=... | The implicit flow puts tokens in the fragment. Current guidance prefers the code flow with PKCE. |
| SecurityReferrer leaks from the callback page | <meta name="referrer" content="no-referrer"> | The callback URL holds a code. Stop the page from sending it in the Referer header. |
| How-toRemove the code from the address bar | history.replaceState({}, "", "/callback-done"); | After you read the code, clean the URL so it does not stay in the history. |
| SecurityCheck the iss parameter on the response | if (params.get("iss") !== expectedIssuer) throw new Error("Wrong issuer"); | With RFC 9207 the response carries iss, which helps detect mix-up attacks. |
| How-toCompare redirect URIs in code | const ok = registered.includes(requestUri); | Use includes or a strict equality check on the raw strings, not a prefix match. |
| SecurityWhy startsWith is a bad redirect check | requestUri.startsWith("https://app.example.com") | An attacker can use https://app.example.com.evil.example. Compare complete strings. |
| How-toParse a redirect URI with the URL class | const u = new URL(redirectUri);
console.log(u.protocol, u.hostname, u.port, u.pathname); | It splits the URI into parts so you can see which part differs. |
| How-toDetect the scheme kind | const isLoopback = ["localhost", "127.0.0.1", "[::1]"].includes(u.hostname); | Loopback hosts are the only place where plain http is normally acceptable. |
FAQ
| Question | Answer |
|---|---|
| What is a redirect URI in OAuth? | A redirect URI is the URL where the authorization server sends the user after sign-in, together with the authorization code and state. You register it in the provider’s app settings, and the request must use a matching value. |
| Why do I get a redirect_uri_mismatch error? | The redirect URI in the authorization request must match one registered URI exactly: scheme, host, port, path, trailing slash and query string. Compare the two strings character by character. Common causes are http instead of https, a trailing slash, and a different port. |
| Can I use wildcard redirect URIs? | Avoid them. The OAuth 2.0 Security Best Current Practice (RFC 9700) requires exact string matching, because a wildcard lets an attacker receive the authorization code on another host or path. Register every exact URI instead. |
| Which redirect URI should a mobile or desktop app use? | For mobile apps, an app-claimed https link (Android App Links or iOS Universal Links) is preferred, or a private-use scheme that uses a reverse domain name such as com.example.app:/callback. Desktop apps usually use a loopback address such as http://127.0.0.1:port/callback. These recommendations come from RFC 8252, and your provider may limit the options. |
| How do I write the OAuth scope parameter? | Use a space-separated list, URL-encoded, for example scope=openid%20profile%20email. Add openid for OpenID Connect and offline_access to ask for a refresh token. Scopes are case-sensitive, and a few providers, such as Facebook, accept commas. |
| Is it safe to use this redirect URI and scope checker? | The check runs in your browser and nothing is uploaded. Redirect URIs and scope names are not secrets, but do not paste authorization codes or tokens. The Share link button puts your input in the URL. |