Developer Docs
sgID v2
sgID v2
  • Introduction
    • Overview
    • Getting Started
      • Register Your Application
      • Integrating With sgID
  • Integrations with sgID
    • TypeScript / JavaScript
      • Framework Guides
        • Express (with Single-Page App frontend)
        • Next.js (client-side rendering)
        • Next.js (server-side rendering)
      • API Reference
    • Python
      • Framework Guides
        • Flask (with Single-Page App frontend)
      • API Reference
    • Custom Integration
    • API Documentation
    • Troubleshooting
  • Learn the basics
    • Protocols
      • OAuth 2.0 and OpenID Connect
      • sgID
        • White Paper
    • Integration Patterns
      • Web Server and SSR Frontend
      • Backend for Single-Page App (SPA) Frontend (BFF)
  • Important Updates
    • User Migrations
      • TypeScript SDK v2.0 Major Release
  • Data Catalog
  • FAQ (Developers)
  • FAQ (Users)
  • Contact
Powered by GitBook
On this page
  • Running the example locally
  • Step 1: Clone the repo
  • Step 2: Update your environment variables
  • Step 3: Run the example
  • Breaking the example down
  • Step 1: Initialize the SDK
  • Step 2: Create the /api/auth-url endpoint
  • Step 3: Create the /api/redirect endpoint
  • Step 4: Create the /api/userinfo endpoint
  • Step 5: Integrate the frontend and backend
  • Congratulations!
Export as PDF
  1. Integrations with sgID
  2. Python
  3. Framework Guides

Flask (with Single-Page App frontend)

Integrating a Flask server with sgID

PreviousFramework GuidesNextAPI Reference

Last updated 1 year ago

This page provides a step-by-step guide on how to integrate the Python SDK in a simple Flask server. This Flask server will be used as a .

To illustrate our example, we have prepared a demo app which will allow you to retrieve your user's name and favorite ice cream flavor after they log in with sgID.

If you have not already obtained your client credentials via registration, please before proceeding. For this example, you should add: 1. [openid, myinfo.name] as the scopes and 2. http://localhost:5001/api/redirect as a redirect URL

Running the example locally

Step 1: Clone the repo

# Clone the frontend repository
git clone https://github.com/opengovsg/sgid-demo-frontend-spa.git
cd sgid-demo-frontend-spa
cat .env.example > .env # Copy the `.env.example` file
npm install

cd ..

# Clone the backend repository
git clone https://github.com/opengovsg/sgid-client-python.git
cd sgid-client-python/examples/flask
cat .env.example > .env # Copy the `.env.example` file
pip install -r requirements.txt

Step 2: Update your environment variables

Update your .env file with your client credentials.

examples/flask/.env
SGID_CLIENT_ID=<your client id>
SGID_CLIENT_SECRET=<your client secret>
SGID_PRIVATE_KEY=<your private key>

Step 3: Run the example

In separate terminals, run the frontend and the backend.

# In the /sgid-client-python/examples/flask directory
flask run

# Open a new terminal and in the /sgid-demo-frontend-spa directory
npm run dev 

If you click on 'Login with Singpass' and authenticate with your Singpass mobile app, you should see your user info on the success screen.

Breaking the example down

In this section, we'll break down the different steps that our example app goes through.

Step 1: Initialize the SDK

In this step, we will create an instance of our SgidClient class which will help us to interface with the sgID server.

In the .env file created from the previous step, fill out your sgID credentials.

examples/flask/.env
SGID_CLIENT_ID=<your client id>
SGID_CLIENT_SECRET=<your client secret>
SGID_PRIVATE_KEY=<your private key>

The main idea here is to load your sgID credentials in a secure way using environment variables instead of hard-coding them into your app.

Next, initialize the SDK by calling the constructor and passing in the environment variables.

index.py
from sgid_client import SgidClient

PORT = 5001

sgid_client = SgidClient(
    client_id=os.getenv("SGID_CLIENT_ID"),
    client_secret=os.getenv("SGID_CLIENT_SECRET"),
    private_key=os.getenv("SGID_PRIVATE_KEY"),
    redirect_uri=f"http://localhost:{PORT}/api/redirect",
)

Before we create the endpoints, we will need to configure the Flask app.

from flask import (
    Flask,
    request,
    make_response,
    redirect,
    abort,
)
from flask_cors import CORS

# In-memory store for user session data
# In a real application, this would be a database.
session_data = {}
SESSION_COOKIE_NAME = "exampleAppSession"

app = Flask(__name__)
# Allow app to interact with demo frontend
frontend_host = "http://localhost:5173"
CORS(app, origins=[frontend_host], supports_credentials=True)

Step 2: Create the /api/auth-url endpoint

When an end user clicks on the sign in button on your application (e.g. 'Login with Singpass app'), it should make a GET request to this endpoint to retrieve the authorization URL. The browser is then redirected to this authorization URL.

The /api/auth-url endpoint should do the following

  • Generate a session ID

  • Generate a PKCE pair (consisting of code challenge and code verifier)

  • Generate an authorization URL

  • Store the code verifier in the session

  • Set the session ID in the browser's cookies

  • Return the authorization URL

index.py
from flask import request
from uuid import uuid4
from urllib.parse import urlencode
from sgid_client import SgidClient, generate_pkce_pair

@app.route("/api/auth-url")
def get_auth_url():
    ice_cream_selection = request.args.get("icecream")
    session_id = str(uuid4())
    # Use search params to store state so other key-value pairs
    # can be added easily
    state = urlencode(
        {
            "icecream": ice_cream_selection,
        }
    )
    # We pass the user's ice cream preference as the state,
    # so after they log in, we can display it together with the
    # other user info.
    code_verifier, code_challenge = generate_pkce_pair()
    url, nonce = sgid_client.authorization_url(
        state=state, code_challenge=code_challenge
    )
    session_data[session_id] = {
        "state": state,
        "nonce": nonce,
        "code_verifier": code_verifier,
    }
    res = make_response({"url": url})
    res.set_cookie(SESSION_COOKIE_NAME, session_id, httponly=True)
    return res

Step 3: Create the /api/redirect endpoint

After the user scans the QR code with their Singpass mobile app and authorizes your application to access the specified scopes, the sgID server will redirect the user's browser to the redirect_uri you specified earlier (either when initializing the SDK or when passed as a parameter to the authorization_url function).

The redirect will include the authorization code and the state (if provided earlier) in the form of query parameters. An example URL would look something like this

http://localhost:5001/api/redirect?
    code=someAuthCode
    &state=someState

The /api/redirect endpoint should do the following

  • Retrieve the authorization code from query params, and the session ID from browser cookies

  • Retrieve the code verifier from session

  • Exchange the authorization code and code verifier for the access token

  • Store the access token and sub in session

  • Redirect the browser to a logged in page (or any page of your choice)

If your application only needs to verify that a user is a real person with a Singpass account without needing to access any government-verified data, then you can stop here and utilize the sub value to identify the user.

index.py
from flask import request, redirect

frontend_host = os.getenv("SGID_FRONTEND_HOST") or "http://localhost:5173"

@app.route("/api/redirect")
def redirect():
    auth_code = request.args.get("code")
    state = request.args.get("state")
    session_id = request.cookies.get(SESSION_COOKIE_NAME)

    session = session_data.get(session_id, None)
    # Validate that the state matches what we passed to sgID for this session
    if session is None or session["state"] != state:
        return redirect(f"{frontend_host}/error")

    sub, access_token = sgid_client.callback(
        code=auth_code, code_verifier=session["code_verifier"], nonce=session["nonce"]
    )
    session["access_token"] = access_token
    session["sub"] = sub
    session_data[session_id] = session

    return redirect(f"{frontend_host}/logged-in")

Step 4: Create the /api/userinfo endpoint

Once the browser has been redirected to a logged in/success page, your app can make a GET request to this endpoint which will use the access token stored in session to request user info from the sgID server.

The /api/userinfo endpoint should do the following

  • Retrieve the session ID from browser cookies

  • Retrieve the access token from memory using the session ID

  • Request user info using the access token

  • Return the user info

index.py
from flask import request, abort
from urllib.parse import parse_qs

@app.route("/api/userinfo")
def userinfo():
    session_id = request.cookies.get(SESSION_COOKIE_NAME)
    session = session_data.get(session_id, None)
    access_token = (
        None
        if session is None or "access_token" not in session
        else session["access_token"]
    )
    if session is None or access_token is None:
        abort(401)
    sub, data = sgid_client.userinfo(sub=session["sub"], access_token=access_token)

    # Add ice cream flavour to userinfo
    ice_cream_selection = parse_qs(session["state"])["icecream"][0]
    data["iceCream"] = ice_cream_selection

    return {"sub": sub, "data": data}

Step 5: Integrate the frontend and backend

Now that your Flask server has been set up properly, you will need to integrate your frontend application with it.

However, if you would like to integrate with your own frontend application, there are two main steps you need to implement:

  1. A page with a 'Login with Singpass' button

    1. The button will need to make a GET request to the /api/auth-url endpoint and then redirect the browser to the received authorization URL.

  2. Fetching the user info after logging in

    1. After the user logs in, the frontend can make a GET request to the /api/userinfo endpoint to retrieve the user info.

You have reached the end of the Flask step-by-step guide.

  1. Utilize a reverse proxy to deploy the frontend and backend on the same domain; or

  2. Set the SameSite attribute as None to be able to set cookies on a different domain

To run the example locally, clone from our by running:

Ensure that your backend Flask server is running on and visit .

If you have followed the steps from , the frontend and backend examples have already been integrated for you.

Click for the relevant code in the frontend repo.

Click for the relevant code in the frontend repo.

Congratulations!

While these examples should work seamlessly in a local environment (i.e. localhost), they may not work if deployed (specifically if the frontend and backend are deployed on different domains). This is due to the attribute on cookies. For these examples to work in a deployed environment, you would need to either

If you want to find out more about how sgID works, click here to .

If you have more questions about sgID, check out our for answers to common questions.

🎉
source code
http://localhost:5001
http://localhost:5173
here
here
SameSite
learn about the sgID protocol
FAQ
Initialize the SDK
Create the /api/auth-url endpoint
Create the/api/redirect endpoint
Create the/api/userinfo endpoint
Test it out
Running the example locally
backend server for a SPA frontend
register your client
sgID login page with an ice cream flavour selector
Clicking 'Login with Singpass app' makes a GET request to the /api/auth-url endpoint