SavePoint

Authentication Portal

OAuth 2.0 Integration Guide

Back to Home

OAuth 2.0 Integration

Complete guide to integrating with SavePoint Authentication

Overview

SavePoint Authentication Portal supports OAuth 2.0 and OpenID Connect (OIDC) standards, allowing your applications to securely authenticate users without handling passwords directly. This guide covers the complete integration process.

OAuth 2.0

Industry-standard authorization framework for secure, delegated access to user data

OpenID Connect

Authentication layer on top of OAuth 2.0 providing user identity information

Getting Started

1. Register Your Application

Before integrating with SavePoint Authentication, you need to register your application:

  1. Contact SavePoint IT Support at support@savepoint.com.au
  2. Provide your application details (name, description, redirect URIs)
  3. Receive your Client ID and Client Secret
  4. Configure your application with the provided credentials

Required Information

  • Application name and description
  • Redirect URIs (must be HTTPS in production)
  • Application type (web, mobile, SPA)
  • Requested scopes and permissions

Authorization Code Flow

The Authorization Code flow is the most secure OAuth 2.0 flow for web applications. It involves redirecting the user to SavePoint for authentication and receiving an authorization code.

Step 1: Authorization Request

Redirect the user to the SavePoint authorization endpoint:

GET https://auth.savepoint.com.au/oauth/authorize?
  response_type=code&
  client_id=YOUR_CLIENT_ID&
  redirect_uri=https://yourapp.com/callback&
  scope=openid profile email&
  state=random_state_value&
  code_challenge=BASE64URL_ENCODED_CHALLENGE&
  code_challenge_method=S256

Step 2: User Authorization

The user will be presented with a login page and consent screen. After successful authentication and consent, they'll be redirected back to your application with an authorization code.

Step 3: Exchange Code for Tokens

Exchange the authorization code for access and ID tokens:

POST https://auth.savepoint.com.au/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=https://yourapp.com/callback&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
code_verifier=ORIGINAL_RANDOM_STRING

Step 4: Use Access Token

Use the access token to make authenticated requests:

GET https://auth.savepoint.com.au/oauth/userinfo
Authorization: Bearer ACCESS_TOKEN

Available Scopes

Scopes define the level of access your application requests from the user:

openid

Required for OpenID Connect. Provides access to the user's unique identifier.

profile

Access to the user's profile information (name, picture, etc.).

email

Access to the user's email address and email verification status.

offline_access

Request a refresh token for long-term access without user interaction.

read:users

Read access to user information (requires admin approval).

manage:sessions

Manage user sessions and authentication state.

Code Examples

JavaScript / Node.js

const express = require('express');
const crypto = require('crypto');
const app = express();

// Configuration
const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
const REDIRECT_URI = 'https://yourapp.com/callback';
const AUTH_BASE_URL = 'https://auth.savepoint.com.au';

// Generate PKCE challenge
function generatePKCE() {
  const codeVerifier = crypto.randomBytes(32).toString('base64url');
  const codeChallenge = crypto
    .createHash('sha256')
    .update(codeVerifier)
    .digest('base64url');
  return { codeVerifier, codeChallenge };
}

// Start OAuth flow
app.get('/login', (req, res) => {
  const { codeVerifier, codeChallenge } = generatePKCE();
  const state = crypto.randomBytes(16).toString('hex');
  
  // Store state and codeVerifier in session
  req.session.state = state;
  req.session.codeVerifier = codeVerifier;
  
  const authUrl = new URL('/oauth/authorize', AUTH_BASE_URL);
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('client_id', CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.set('scope', 'openid profile email');
  authUrl.searchParams.set('state', state);
  authUrl.searchParams.set('code_challenge', codeChallenge);
  authUrl.searchParams.set('code_challenge_method', 'S256');
  
  res.redirect(authUrl.toString());
});

// Handle callback
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;
  
  if (state !== req.session.state) {
    return res.status(400).send('Invalid state');
  }
  
  try {
    const tokenResponse = await fetch(`${AUTH_BASE_URL}/oauth/token`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code,
        redirect_uri: REDIRECT_URI,
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
        code_verifier: req.session.codeVerifier,
      }),
    });
    
    const tokens = await tokenResponse.json();
    
    // Store tokens securely
    req.session.accessToken = tokens.access_token;
    req.session.idToken = tokens.id_token;
    
    res.redirect('/dashboard');
  } catch (error) {
    console.error('Token exchange failed:', error);
    res.status(500).send('Authentication failed');
  }
});

React / Next.js

import { createAuthClient } from '@better-auth/react';

const authClient = createAuthClient({
  baseURL: 'https://auth.savepoint.com.au',
  plugins: [
    // Add any required plugins
  ],
});

export default function LoginButton() {
  const { signIn, data: session, isPending } = authClient.useSession();
  
  const handleLogin = async () => {
    try {
      await signIn.social({
        provider: 'savepoint',
        callbackURL: '/dashboard',
      });
    } catch (error) {
      console.error('Login failed:', error);
    }
  };
  
  if (session) {
    return (
      <div>
        <p>Welcome, {session.user.name}!</p>
        <button onClick={() => authClient.signOut()}>
          Sign Out
        </button>
      </div>
    );
  }
  
  return (
    <button 
      onClick={handleLogin}
      disabled={isPending}
      className="bg-blue-600 text-white px-4 py-2 rounded"
    >
      {isPending ? 'Signing in...' : 'Sign in with SavePoint'}
    </button>
  );
}

Security Considerations

Critical Security Requirements

  • Always use HTTPS for redirect URIs in production
  • Implement PKCE (Proof Key for Code Exchange) for public clients
  • Validate the state parameter to prevent CSRF attacks
  • Store client secrets securely (never in client-side code)
  • Implement proper token storage and rotation

Token Security

  • • Store tokens securely (encrypted cookies, secure storage)
  • • Implement automatic token refresh
  • • Set appropriate token expiration times
  • • Log and monitor token usage

Application Security

  • • Validate all redirect URIs
  • • Implement rate limiting
  • • Use secure session management
  • • Regular security audits

Well-Known Endpoints

SavePoint Authentication provides standard discovery endpoints for automatic configuration:

/.well-known/openid_configuration

OpenID Connect Discovery document containing endpoint URLs and supported features.

View Discovery Document →

/.well-known/oauth-authorization-server

OAuth 2.0 Authorization Server Metadata document.

View OAuth Metadata →

Testing Your Integration

Development Environment

  • Use http://localhost URLs for local testing
  • Test all OAuth flows (authorization code, refresh token)
  • Verify error handling for invalid credentials and expired tokens
  • Test PKCE implementation for security

Common Issues

Redirect URI Mismatch

Ensure the redirect URI in your request exactly matches the one registered with SavePoint.

Invalid Client Credentials

Verify your Client ID and Client Secret are correct and haven't been rotated.

CORS Issues

For client-side applications, ensure your domain is registered for CORS access.