SSO

how to implement SSO?

server

https://github.com/DAVIDhaker/django-sso

react client:

index.jsx:

import { createBrowserRouter } from "react-router-dom";
import SSO from "./server/auth";

const router = createBrowserRouter([
  {
    path: "sso/accept",
    element: <SSO />,
  },
]);

serve/auth.jsx:

import { useNavigate } from "react-router-dom";

export async function fetchAuthInfo(authToken) {
  let formData = new FormData();
  formData.append("token", process.env.REACT_APP_AUTH_SECRET);
  formData.append("authentication_token", authToken);
  const response = await fetch(process.env.REACT_APP_AUTH_GET, {
    method: "POST",
    body: formData,
  });
  return response.json();
}

async function confirmAuth(authToken) {
  let formData = new FormData();
  formData.append("token", process.env.REACT_APP_AUTH_SECRET);
  formData.append("authentication_token", authToken);
  const response = await fetch(process.env.REACT_APP_AUTH_CONFIRM, {
    method: "POST",
    body: formData,
  });
  return response.json();
}

export function useToken() {
  const getToken = () => {
    return localStorage.getItem("token");
  };

  const [token, setToken] = useState(getToken());

  const saveToken = (userToken) => {
    localStorage.setItem("token", userToken);
    setToken(userToken);
  };

  return {
    setToken: saveToken,
    token,
  };
}

export function useUser() {
  const getUser = () => {
    return localStorage.getItem("user");
  };

  const [user, setUser] = useState(getUser());

  const saveUser = (user) => {
    localStorage.setItem("user", user);
    setUser(user);
  };

  return {
    setUser: saveUser,
    user,
  };
}


export default function SSO() {
  const navigate = useNavigate();
  const { token } = useToken();
  const { setUser } = useUser();

  useEffect(() => {
    async function fetchData() {
      const response = await fetchAuthInfo(token);
      setUser(response.user_identy);
      await confirmAuth(token);
      navigate(response.next_url);
    }
    fetchData();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return null;
}

flask client

from functools import wraps

import requests
from flask import Blueprint, current_app, request, session

from . import db
from .models import User

bp = Blueprint('sso', __name__, url_prefix='/sso/')


def auth(view_function):
    """decorator of view function need auth
    """

    @wraps(view_function)
    def wrapper(*args, **kwargs):
        request_token = request.headers.get("Authorization")
        if not request_token:
            return dict(error="请登录"), 401
        username = session.get("username")
        if not (request_token == session.get("token") and username):
            try:
                response = requests.post(
                    current_app.config["SSO_AUTH_GET_URL"],
                    data={
                        "token": current_app.config["SSO_AUTH_SECRET"],
                        "authentication_token": request_token
                    },
                    timeout=5)
            except requests.exceptions.ConnectionError:
                return dict(error="认证服务连接不上, 请联系管理员"), 401
            auth_info = response.json()
            if auth_info.get("error"):
                return dict(error="认证失败"), 401
            username = auth_info["user_identy"]
            session["username"] = username
            session["token"] = request_token
        user = db.session.execute(
            db.select(User).filter_by(username=username)).scalar()
        if not user:
            user = User(username=username)
            db.session.add(user)
            db.session.commit()
        return view_function(*args, **kwargs, user=user)

    return wrapper


def permission(role):
    """decorator of view function need permission
    """

    def decorator(view_function):
        """decorator
        """

        @wraps(view_function)
        def wrapper(*args, **kwargs):
            if not kwargs.get("user").has_permission(role):
                return "您没有权限", 403
            return view_function(*args, **kwargs)

        return wrapper

    return decorator

# `/sso/event/` api can sync data with server:
@bp.route("/event/", methods=("POST", ))
def event():
    """event
    """
    data = request.json
    event_type = data.get("type")
    if event_type == "update_account":
        fields = data.get("fields")
        username = fields.get("user_identy")
        user = db.session.query(User).filter_by(username=username).first()
        first_name = fields.get("first_name")
        if user:
            if first_name != user.first_name:
                user.first_name = first_name
                db.session.commit()
        else:
            user = User(username=username, first_name=first_name)
            db.session.add(user)
            db.session.commit()
    return {"ok": True}