Sign in on a website

Authorization code flow with a client secret held by your server. People never type a password into your site.

Standard OpenID Connect, so any OIDC library or framework plugin works. Discovery: https://api.unet.ovh/oidc/.well-known/openid-configuration. Your server authenticates to the token endpoint with client_secret_basic (secret in the Authorization header; client_secret_post is not accepted, and a few libraries default to it, so set it explicitly). PKCE (S256) is supported and recommended.

// npm i openid-client  (any OpenID Connect library works the same way)
import * as client from "openid-client";

// UNET wants the secret in the Authorization header (client_secret_basic); openid-client defaults to the request body
const config = await client.discovery(
  new URL("https://api.unet.ovh/oidc"),
  "YOUR_APP_ID",
  undefined,
  client.ClientSecretBasic("YOUR_APP_SECRET"),
);

// 1. send the person to UNET
const codeVerifier = client.randomPKCECodeVerifier();
const state = client.randomState();
const url = client.buildAuthorizationUrl(config, {
  redirect_uri: "https://your-site.example/auth/unet/callback",
  scope: "openid profile",
  code_challenge: await client.calculatePKCECodeChallenge(codeVerifier),
  code_challenge_method: "S256",
  state,
});
// keep codeVerifier + state in the session, then redirect to url.href

// 2. on your callback URL
const tokens = await client.authorizationCodeGrant(config, new URL(req.url), {
  pkceCodeVerifier: codeVerifier,
  expectedState: state,
});
const { sub } = tokens.claims();                                   // the id you store
const profile = await client.fetchUserInfo(config, tokens.access_token, sub);
// profile: { sub, username, display_name, picture, account_created_at }
// Auth.js / NextAuth: a custom OpenID Connect provider
import NextAuth from "next-auth";

export const { handlers, auth } = NextAuth({
  providers: [
    {
      id: "unet",
      name: "UNET",
      type: "oidc",
      issuer: "https://api.unet.ovh/oidc",
      clientId: process.env.UNET_CLIENT_ID,
      clientSecret: process.env.UNET_CLIENT_SECRET,
      authorization: { params: { scope: "openid profile" } },
      checks: ["pkce", "state"],
      profile: (p) => ({
        id: p.sub,
        name: p.display_name ?? p.username,
        image: p.picture ?? null,
      }),
    },
  ],
});