Skip to main content
The information you will need:
  • Auth Server URL: https://auth.civic.com/oauth/
  • Client ID: Provided on sign-up at auth.civic.com
  • Scopes: openid email profile

The Civic Auth Server

At its core, Civic Auth is an OAuth 2.0 auth server. It supports the authorization code grant type with PKCE.
If you are looking for other OAuth 2 grant types, we’d like to hear from you in our developer community.

Sample Call to the OAuth Server

To trigger a login process, simply call the oauth server as follows:
https://auth.civic.com/oauth/auth
    ?response_type=code
    &client_id={clientId}
    &redirect_uri={redirectUri}
    &scope=openid email profile
    &state={state}
    &code_challenge={codeChallenge}
    &code_challenge_method=S256

Query Parameters:

  • client_id: Your application’s unique identifier provided by Civic Auth.
  • redirect_uri: The URL to which users should be redirected after authentication.
  • scope: The permissions your application is requesting (e.g., openid email profile). Scopes must be space-separated as per OAuth 2.0 RFC6749 specification.
  • state: A random string to maintain state between the request and callback.
  • code_challenge: A code challenge derived from the code verifier for PKCE.
Civic Auth requires the use of PKCE (Proof Key for Code Exchange), so thecode_challenge parameter is obligatory. For more information, see PKCE (Proof Key for Code Exchange).

Example

See below for an example of using Civic Auth with a third-party library: OAuth 4 WebAPI
index.html
<!DOCTYPE html>
<html>
<body>
<button id="login" disabled onclick="location.href=authURL">login</button>
<div id="information"></div>
</body>

<script>

const libraryCDN = "https://cdn.jsdelivr.net/npm/oauth4webapi@2.10.3/+esm"
const openIdConnectUrl = new URL("https://auth.civic.com/oauth/");
const clientId = "YOUR CLIENT ID";
const redirectUri = "https://localhost:3000";
const scope = "openid email profile";

// Define currentUrl at top-level scope
const currentUrl = new URL(location.href);

const buttonEl = document.querySelector("#login")
const informationEl = document.querySelector("#information")

var authURL = ""
let as
let codeVerified

import(libraryCDN).then((oauth) => {
  const client = {
    client_id: clientId,
    token_endpoint_auth_method: "none",
  }
  async function discover(){
    as = await oauth
      .discoveryRequest(openIdConnectUrl, { algorithm: "oidc" })
      .then((response) => oauth.processDiscoveryResponse(openIdConnectUrl, response))
    if (currentUrl.searchParams.get("code")) {
      codeVerified = sessionStorage.getItem("code_verifier" )
      getInfo()
    }

    const code_challenge_method = "S256"
    const code_verifier = oauth.generateRandomCodeVerifier()
    sessionStorage.setItem("code_verifier", code_verifier)
    const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier)

    const authorizationUrl = new URL(as.authorization_endpoint)
    authorizationUrl.searchParams.set("client_id", clientId)
    authorizationUrl.searchParams.set("redirect_uri", redirectUri)
    authorizationUrl.searchParams.set("response_type", "code")
    authorizationUrl.searchParams.set("scope", scope)
    authorizationUrl.searchParams.set("code_challenge", code_challenge)
    authorizationUrl.searchParams.set("code_challenge_method", code_challenge_method)

    authURL = authorizationUrl.href
    // Enable the login button once authURL is ready
    buttonEl.disabled = false
  }

  discover()
  async function getInfo(){
    const params = oauth.validateAuthResponse(as, client, currentUrl)

    if (oauth.isOAuth2Error(params)) {
      throw new Error() // Handle OAuth 2.0 redirect error
    }

    const response = await oauth.authorizationCodeGrantRequest(
      as,
      client,
      params,
      redirectUri,
      codeVerified,
    )
    const result = await oauth.processAuthorizationCodeOpenIDResponse(as, client, response )

    const { access_token } = result
    const claims = oauth.getValidatedIdTokenClaims(result)

    const { sub } = claims;

    const responseInfo = await oauth.userInfoRequest(as, client, access_token)

    const resultInfo = await oauth.processUserInfoResponse(as, client, sub, responseInfo)
    informationEl.innerText = "Welcome "+resultInfo.preferred_username+" ("+resultInfo.name+")"
  }
})

</script>

</html>

Refreshing a session

The Civic OAuth server supports the token refresh flow by calling the oauth server as follows:
https://auth.civic.com/oauth/token
    ?refresh_token={refreshToken}
    &client_id={clientId}
    &grant_type=refresh_token

Query Parameters:

  • client_id: Your application’s unique identifier provided by Civic Auth.
  • refresh_token: The refresh token from the user’s existing session
  • grant_type: a string ‘refresh_token’
If the refresh token is valid this will return a JSON response containing updated tokens e.g.
{
    "access_token": <JWT with sub=userId>
    "id_token": <JWT with sub=userId, profile info & forwardedTokens>
    "refresh_token": <string>
    "expires_in": <how many seconds until the token expires>
}

Usage

The Civic Auth SDK is designed to simplify front-end integration, with optimized support for React and Next.js. However, if your frontend uses another framework, you can still retrieve user information after login by inspecting the ID token. The ID token is produced after completing the login process. A common pattern is for your backend to pass that token to your frontend as a cookie. Here’s an example of how to access user information in vanilla JavaScript by reading the ID token cookie:
This example uses browser-compatible JWT decoding. The jsonwebtoken library is designed for Node.js and won’t work in browser environments. For production applications, consider using a dedicated browser JWT library like jwt-decode.
Security Note: The manual decoding approach below only extracts the JWT payload without validating the signature. In production environments, you should always validate the JWT signature to ensure the token is authentic and hasn’t been tampered with. We recommend using Civic Auth Verify for easy token validation, or you can manually validate against Civic’s public keys from the JWKS endpoint at https://auth.civic.com/oauth/jwks.
// Option 1: Manual base64 decoding (no dependencies)
function decodeJWT(token) {
  try {
    const payload = token.split('.')[1];
    const decoded = JSON.parse(atob(payload));
    return decoded;
  } catch (error) {
    console.error('Error decoding JWT:', error);
    return null;
  }
}

// Option 2: Use a browser-compatible library like 'jwt-decode'
// First install: npm install jwt-decode
// import jwt_decode from 'jwt-decode';

function getUserFromToken() {
  const cookies = document.cookie.split('; ');
  const tokenCookie = cookies.find(row => row.startsWith('id_token='));
  
  if (!tokenCookie) return null;
  
  // Handle JWT tokens that may contain = characters
  const token = tokenCookie.substring('id_token='.length);
  
  // Use either approach:
  return decodeJWT(token);           // Option 1: Manual decoding
  // return jwt_decode(token);       // Option 2: Using jwt-decode library
}

const user = getUserFromToken();
console.log(user); // Log user info or use it in your app