Skip to main content

Add hosted login with OAuth and OpenID Connect

This guide lets you try SyAuth's login screen, then explains how to connect it to an application. Complete dashboard setup first.

After you click Approve, the browser leaves SyAuth and opens the return address you registered. This return address is called the callback URL or redirect_uri. Something must be running at that address to receive the result. An address containing localhost or 127.0.0.1 points to the computer running your browser.

Choose the instructions that match your situation:

Your situationWhere to start
You want to try login and have no app or programming experienceFollow Manual test without an application below. The provided script receives the result for you and generates the URL to open.
You are writing the sign-in code for an appFollow Implement the flow in your application.
Your app already has a Sign in button connected to an OAuth/OIDC libraryStart with that button so the library saves the login attempt. Use the steps below to understand or troubleshoot the flow.

Manual test without an application​

You can try login without building a web app or writing JavaScript. The small Python program below receives SyAuth's response on your computer. Keep it running until you have approved access and received the result.

Before starting, you need:

  • Python 3.8 or later installed on the computer where you will open the browser. If needed, get an installer for your system from Python's download page.
  • A public OAuth client created during dashboard setup. Copy its client ID.
  • A test user in that client's workspace, with an email you can access. Use the test user you already created, or register on the hosted login screen. Your dashboard account is separate.
  1. In Applications, add http://127.0.0.1:8080/callback to this client's redirect URIs. Keep authorization_code enabled and allow openid, profile and email scopes. This receiver uses HTTP on the loopback interface; copy the URI exactly.
  2. Download the callback receiver and save it as oauth-callback-test.py in your Downloads folder. If the browser displays the file as text, use Save link as on the download link and keep the .py filename.
  3. Open Terminal on macOS/Linux, or Command Prompt (CMD) on Windows. The command below uses the file in Downloads, so you do not need to change folders first. Replace YOUR_PUBLIC_CLIENT_ID with the client ID you copied, paste the complete command, and press Enter. Keep the terminal open.
python3 "$HOME/Downloads/oauth-callback-test.py" --client-id YOUR_PUBLIC_CLIENT_ID
  1. The terminal prints a long URL starting with https://api.syauth.com/e/v1/oauth/authorize/. Copy the whole URL and open it in your browser. Use the URL printed by this run of the script; you do not need the JavaScript generator or the five-placeholder URL further down this page.
  2. SyAuth shows a login screen if you are not already signed in. Sign in as your test user. If you register a new user, complete email verification when prompted.
  3. If a permissions screen appears, check the application name and click Approve. This allows the application to receive the identity information listed on that screen. If the client approves automatically, the browser goes directly to the next step.
  4. The browser should open http://127.0.0.1:8080/callback?... and display “Callback received and state matched. Return to your terminal.” This means the program received the response and matched it to the test you started. Return to the terminal; it prints the values needed for the next request.

At this point, you have tested login, approval and the return to your computer. To continue testing the API, use the printed values in step 3's token request, then step 4. A token is the credential that request returns; it lets you make the next request for the signed-in user's identity.

If you see an error instead, use this table before continuing:

What you seeWhat to do
python3 or py is not foundInstall Python 3.8 or later, reopen your terminal, then run the command again.
The script file cannot be foundCheck that oauth-callback-test.py is saved in Downloads and the filename does not end in .py.txt. If you saved it elsewhere, use that file's full path in the command instead.
Cannot listen on 127.0.0.1:8080The program could not open the local return address. Check whether another program is using port 8080 or your computer blocks the listener, then retry.
SyAuth rejects the redirect URIIn the client's settings, add exactly http://127.0.0.1:8080/callback. Keep http, 127.0.0.1, port 8080 and /callback unchanged.
After Approve, the browser says it cannot connectCheck that the script is still running on the same computer as the browser. If it stopped, run it again and use its new URL.
After Approve, you end up at https://localhost:3000/auth/error or see Invalid or expired sessionThis is not the return address used by this test. Rerun the script and open the new URL it prints. For this test, the URL's redirect_uri must be http://127.0.0.1:8080/callback, encoded inside the URL. If the new URL still ends elsewhere, keep the final address and exact error for support; the message alone does not identify its source.
The receiver says Missing or mismatched stateThe response does not match the test currently running. Run the script again and open only its new URL.
The terminal reports an OAuth error, such as access_deniedRead that error before continuing. If you declined access and want to retry, rerun the script and approve the new request.

The receiver waits up to 10 minutes and accepts one valid callback. If port 8080 is already in use, stop that listener before retrying. If the attempt expires or you need another test, rerun the script and open its new URL. Keep the test output private. The receiver checks state and captures the code; token exchange and ID-token validation are separate steps below. It does not establish an application session.

https://localhost:3000/auth/callback is an example application route, not a callback service supplied by SyAuth. Registering it does not create a listener there. For the test above, use http://127.0.0.1:8080/callback in the registered client and in the token request.

Implement the flow in your application​

The remaining steps are for developers implementing the callback and token handling in an app. Use authorization code with S256 PKCE, as shown below. If you are following the manual test above, the script has already generated and saved the values from step 2; continue at step 3 with its printed output.

Replace the YOUR_... placeholders directly with your values. Curl examples require no shell environment setup, but each later step uses values from the same sign-in attempt. Windows examples use Command Prompt (CMD).

1. Discover endpoints​

curl -sS https://api.syauth.com/e/v1/.well-known/openid-configuration

Use the returned authorization_endpoint, token_endpoint, userinfo_endpoint and jwks_uri. Pin the expected issuer in your application's configuration. The examples below use the standard SyAuth API root.

2. Create a sign-in attempt​

Your application generates state and nonce; you do not obtain them from the SyAuth dashboard or an API. Generate two independent values for every sign-in attempt:

  • state links the callback to the sign-in attempt. Compare the callback's state with the saved value before exchanging the code.
  • nonce links the ID token to the sign-in attempt. Compare the ID token's nonce claim with the saved value during ID token validation.

PKCE also needs a random code verifier and its derived S256 code challenge. Send the challenge in the authorization URL and keep the verifier for the token exchange. They are different values.

For a manual test, paste this entire block into your browser's developer console on an HTTPS page or your localhost app. It wraps await inside an async function, so it does not require top-level await support and can be run again without redeclaring console variables. In your application, run equivalent code before starting each sign-in:

(async () => {
function randomHex() {
return Array.from(crypto.getRandomValues(new Uint8Array(32)), (byte) =>
byte.toString(16).padStart(2, "0")
).join("");
}

const state = randomHex();
const nonce = randomHex();
const codeVerifier = randomHex();
const digest = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(codeVerifier)
);
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");

console.log({ state, nonce, codeVerifier, codeChallenge });
})().catch(console.error);

The console may first show a Promise, then the object containing the four generated values. If you previously saw Unexpected identifier 'crypto' on the await crypto.subtle.digest(...) line, use the complete wrapped example above.

Copy the generated state into YOUR_RANDOM_STATE and nonce into YOUR_RANDOM_NONCE. Each value contains 32 cryptographically random bytes (256 bits), encoded as 64 hexadecimal characters using only 0–9 and a–f. This is a recommended URL-safe format, not a requirement to use hexadecimal. Do not use Math.random(), timestamps, user IDs or fixed example strings. See Web Crypto's getRandomValues() for the generator used here.

The generated codeVerifier also has 64 hexadecimal characters. A PKCE verifier must have 43–128 characters, using only letters, digits, -, ., _ or ~. Its S256 challenge is Base64URL(SHA-256(verifier)) without = padding; the generator produces a 43-character challenge. See RFC 7636.

Keep state, nonce and codeVerifier in your application's short-lived session, tied to this sign-in attempt, until callback validation is complete. Discard them after completion or expiry; generate fresh values for the next attempt. The console output is for this manual test; your application must save the values before redirecting the browser. Do not log them in production.

Console generation does not save an application session. Pasting a manually constructed URL into the browser can reach hosted login and consent, then fail at your application's callback because its library has no matching saved attempt. Start from the app's sign-in action when testing an existing integration. For a manual protocol test, use a callback handler you control that validates the values you saved for that test; an existing library's callback may reject the request before you can inspect it. Adding arbitrary session-storage entries does not initialize a library that expects its own session or cookie format.

Replace the placeholders in the authorization URL:

PlaceholderWhat to use
YOUR_PUBLIC_CLIENT_IDThe OAuth client_id saved during application registration.
YOUR_URL_ENCODED_REDIRECT_URIYour application's exact registered callback URI, URL-encoded as a query parameter value. For example, http://127.0.0.1:8080/callback becomes http%3A%2F%2F127.0.0.1%3A8080%2Fcallback. You can encode your own URI with JavaScript's encodeURIComponent("YOUR_REDIRECT_URI").
YOUR_RANDOM_STATEThe state generated above for this attempt.
YOUR_RANDOM_NONCEThe independently generated nonce above for this attempt.
YOUR_S256_CODE_CHALLENGEThe generated codeChallenge. Keep codeVerifier for step 3; do not put it in this URL.

Before opening this URL: start the application that handles your callback and save this attempt's state, nonce and verifier. If you have no application, follow Manual test without an application instead; its script prints a ready-to-open URL and handles the return for you. Do not substitute a console-generated URL for the script's URL, because the script would have saved different values.

Register the callback URI on your OAuth client before starting. The decoded redirect_uri must exactly match the registered value, including scheme, host, port, path and any trailing slash. Keep the original, unencoded URI for the token request in step 3. After preparing the callback and replacing all five placeholders, open the complete URL in your browser:

https://api.syauth.com/e/v1/oauth/authorize/?client_id=YOUR_PUBLIC_CLIENT_ID&redirect_uri=YOUR_URL_ENCODED_REDIRECT_URI&response_type=code&scope=openid%20profile%20email&state=YOUR_RANDOM_STATE&nonce=YOUR_RANDOM_NONCE&code_challenge=YOUR_S256_CODE_CHALLENGE&code_challenge_method=S256

What happens after you open the URL​

  1. Sign in on SyAuth. If you are not already signed in, SyAuth shows its login screen. Use a test user belonging to this workspace. If you choose to register, use an email you can access and complete verification when prompted. Your dashboard account is separate from this test user.
  2. Review access. SyAuth shows the application's name and requested permissions. Click Approve to continue, or decline to cancel. The permission screen may be skipped if the client is configured to approve automatically.
  3. Return to the callback. After approval, the browser leaves SyAuth and opens your redirect_uri, with code and state added to its address. SyAuth supplies the authorization code. What the page displays now depends on the code running at that callback address; SyAuth does not supply an application success page there.
  4. Continue with step 3 below. Your callback checks the saved attempt and exchanges the code for tokens. Reaching the login or approval screen alone does not complete this exchange.

If the return address is localhost or 127.0.0.1 and nothing is running there, the browser will normally show a connection error after approval. If a callback displays “Invalid or expired session”, inspect whether it has the saved attempt or is forwarding an earlier error; the text alone does not prove that the user's account or authorization code expired. Use the manual-test error table if you are testing without an app.

3. Validate the callback and exchange the code​

If you used the no-app test receiver, stay in your terminal. The receiver has already checked state and printed the values named YOUR_PUBLIC_CLIENT_ID, YOUR_AUTHORIZATION_CODE, YOUR_PKCE_CODE_VERIFIER and YOUR_REDIRECT_URI. Replace each matching placeholder in the command below with its printed value. The next command requests tokens; it is not another URL to open in the browser.

Your callback receives code and state, or an OAuth error. Reject a missing or mismatched state before exchanging the code. Each authorization code is short-lived and single-use. Use the original verifier and the exact same callback URI.

For example, a successful local redirect has this shape:

http://127.0.0.1:8080/callback?code=CODE_ISSUED_BY_SYAUTH&state=STATE_YOU_SAVED

Your callback handler reads these query parameters, for example with new URL(callbackUrl).searchParams. For a manual test, inspect the callback URL in the browser's address bar. The authorization code comes from SyAuth after sign-in; do not generate one or substitute a verification-email code. If the callback contains error instead of code, handle that error before attempting a token request.

For a public client, replace YOUR_PUBLIC_CLIENT_ID with your OAuth client ID, YOUR_AUTHORIZATION_CODE with the callback's decoded code, YOUR_PKCE_CODE_VERIFIER with the original verifier saved by the receiver or your step-2 code, and YOUR_REDIRECT_URI with the original, unencoded callback URI. Do not include the callback's ?code=...&state=... query in redirect_uri. The --data-urlencode option encodes the values for you. Copy the complete command from the tab for your system into your terminal, then press Enter:

curl -i "https://api.syauth.com/e/v1/oauth/token/" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode "client_id=YOUR_PUBLIC_CLIENT_ID" \
--data-urlencode "code=YOUR_AUTHORIZATION_CODE" \
--data-urlencode "redirect_uri=YOUR_REDIRECT_URI" \
--data-urlencode "code_verifier=YOUR_PKCE_CODE_VERIFIER"

A confidential client also authenticates with its client secret on its server. Use HTTP Basic client authentication or the documented body credentials. Never embed a confidential secret in browser JavaScript.

The expected result is HTTP 200, followed by a JSON response containing access_token. Copy that field's complete value for step 4. If the response has error instead, resolve it before continuing. In particular, an expired or already-used code requires a new login attempt and a new code; repeating the same token request will not create a new attempt.

A successful response can include:

{
"access_token": "OAUTH_ACCESS_TOKEN",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "OAUTH_REFRESH_TOKEN",
"scope": "openid profile email",
"id_token": "OIDC_ID_TOKEN"
}

Fields depend on the grant and scopes; use the actual expires_in returned. For the manual API test, continue with the returned access token in step 4.

For developers creating an application session: validate an ID token with a suitable OIDC/JWT library: signature using the issuer's JWKS, allowed algorithm, exact issuer, your client ID as audience, expiry, and the original nonce. Decoding the JWT payload alone is insufficient. Establish your application's session only after validation.

4. Read the user's identity​

Replace YOUR_OAUTH_ACCESS_TOKEN with access_token from the token response in step 3. Keep Authorization: Bearer exactly as shown. Do not use id_token, refresh_token or a developer token here.

curl -i "https://api.syauth.com/e/v1/oauth/userinfo/" \
-H "Authorization: Bearer YOUR_OAUTH_ACCESS_TOKEN"

Expect 200 with sub and claims allowed by the granted scopes, such as email/name. Use sub as the stable user identifier, rather than an editable email address.

For the manual test, HTTP 200 and identity details belonging to your test user complete the login/API walkthrough. sub is the user's stable identifier. If the request returns 401, first check that you copied access_token from the successful token response; an ID token or developer token will not work for this request.

5. Refresh and sign out​

This is a separate lifecycle test after the basic walkthrough. Refresh means getting a new access token without asking the user to sign in again. Revocation means asking SyAuth to invalidate a token. For a first manual test, complete steps 3 and 4 before trying these requests.

If step 3 issued a refresh_token and the client allows the refresh_token grant, use the following public-client request. Replace YOUR_OAUTH_REFRESH_TOKEN with the latest refresh_token from the token response and use the same client ID:

curl -i "https://api.syauth.com/e/v1/oauth/token/" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode "client_id=YOUR_PUBLIC_CLIENT_ID" \
--data-urlencode "refresh_token=YOUR_OAUTH_REFRESH_TOKEN"

Use the new access_token and store any replacement refresh_token returned. If no refresh token was issued, start a new sign-in when needed. A confidential client must also authenticate with its server-held secret, as in step 3.

On sign-out, clear your application's session and revoke the OAuth token you want to invalidate. This example revokes an access token for a public client:

curl -i "https://api.syauth.com/e/v1/oauth/revoke/" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "client_id=YOUR_PUBLIC_CLIENT_ID" \
--data-urlencode "token=YOUR_OAUTH_ACCESS_TOKEN" \
--data-urlencode 'token_type_hint=access_token'

To revoke a refresh token, use its value for token and set token_type_hint=refresh_token. Confidential clients also authenticate with their secret. Successful revocation returns HTTP 200 with {}. OAuth revocation and your application's session cleanup are separate actions.

If token exchange returns invalid_grant, check the original verifier and redirect URI, then start a fresh sign-in attempt. If the browser cannot reach the callback, check that your app is running at the registered host, port and path. See OAuth troubleshooting.

If approval ends at your app's /auth/error page with an error such as Invalid or expired session, inspect the callback handler and the preceding network requests. Check that login started from your app, that the callback can read the original saved attempt, and that the browser stayed on the same application origin (for example, do not switch between http://localhost, https://localhost and 127.0.0.1). The app may also forward an upstream error, so keep the exact response from the failed step. Creating an application user does not create the browser's pending OAuth attempt.

Use the OAuth API reference for exact request fields and errors. Password grant is not supported. Client-generated PKCE avoids relying on the optional server-side PKCE initializer, which has an availability limitation.