Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // server.js (CommonJS) — full working example
- // Uses only openid-client documented methods: discovery, PKCE helpers,
- // buildAuthorizationUrl, authorizationCodeGrant, fetchProtectedResource.
- require('dotenv').config();
- const express = require('express');
- const session = require('express-session');
- const client = require('openid-client');
- const https = require('https');
- const app = express();
- const PORT = process.env.PORT || 3000;
- const REDIRECT_URI = process.env.REDIRECT_URI || `http://localhost:${PORT}/callback`;
- const SCOPE = 'openid profile';
- // simple JSON fetch (no extra deps)
- function fetchJson(url) {
- return new Promise((resolve, reject) => {
- https
- .get(url, (res) => {
- let data = '';
- res.on('data', (chunk) => (data += chunk));
- res.on('end', () => {
- try {
- resolve(JSON.parse(data));
- } catch (e) {
- reject(e);
- }
- });
- })
- .on('error', reject);
- });
- }
- app.use(
- session({
- secret: process.env.SESSION_SECRET || 'dev-secret',
- resave: false,
- saveUninitialized: true,
- })
- );
- let cachedConfig = null;
- async function getConfig() {
- if (cachedConfig) return cachedConfig;
- // openid-client expects a URL instance for discovery
- const issuerBase = new URL(process.env.CODEFORCES_ISSUER || 'https://codeforces.com');
- cachedConfig = await client.discovery(
- issuerBase,
- process.env.CODEFORCES_CLIENT_ID,
- process.env.CODEFORCES_CLIENT_SECRET
- );
- console.log('Discovered issuer:', cachedConfig.serverMetadata().issuer);
- return cachedConfig;
- }
- app.get('/', (req, res) => {
- res.send(`<a href="/login">Login with Codeforces</a>`);
- });
- app.get('/login', async (req, res) => {
- try {
- const config = await getConfig();
- const code_verifier = client.randomPKCECodeVerifier();
- const code_challenge = await client.calculatePKCECodeChallenge(code_verifier);
- const state = client.randomState();
- req.session.code_verifier = code_verifier;
- req.session.state = state;
- const parameters = {
- redirect_uri: REDIRECT_URI,
- scope: SCOPE,
- code_challenge,
- code_challenge_method: 'S256',
- state,
- };
- const redirectTo = client.buildAuthorizationUrl(config, parameters);
- return res.redirect(redirectTo.href);
- } catch (err) {
- console.error('login error', err);
- return res.status(500).send('Login error');
- }
- });
- app.get('/callback', async (req, res) => {
- try {
- const config = await getConfig();
- const callbackUrl = new URL(`${REDIRECT_URI}?${new URLSearchParams(req.query).toString()}`);
- const tokens = await client.authorizationCodeGrant(config, callbackUrl, {
- pkceCodeVerifier: req.session.code_verifier,
- expectedState: req.session.state,
- });
- console.log('Received token keys:', Object.keys(tokens).join(', '));
- // 1) Try userinfo endpoint (if provided)
- let userinfo = null;
- const userinfoEndpoint =
- (typeof config.serverMetadata === 'function' && config.serverMetadata().userinfo_endpoint) ||
- null;
- if (userinfoEndpoint && tokens.access_token) {
- try {
- const userinfoResponse = await client.fetchProtectedResource(
- config,
- tokens.access_token,
- new URL(userinfoEndpoint),
- 'GET'
- );
- userinfo = await userinfoResponse.json();
- console.log('userinfo endpoint result:', userinfo);
- } catch (e) {
- console.warn('userinfo endpoint failed or returned empty:', e && e.message);
- userinfo = null;
- }
- }
- // 2) Decode id_token if userinfo empty
- function decodeIdToken(idToken) {
- try {
- const parts = idToken.split('.');
- if (parts.length < 2) return null;
- let b = parts[1].replace(/-/g, '+').replace(/_/g, '/');
- while (b.length % 4) b += '=';
- return JSON.parse(Buffer.from(b, 'base64').toString('utf8'));
- } catch {
- return null;
- }
- }
- let claims = null;
- if ((!userinfo || Object.keys(userinfo).length === 0) && tokens.id_token) {
- claims = decodeIdToken(tokens.id_token);
- console.log('id_token claims:', claims);
- }
- // 3) Choose a name/handle from userinfo or claims
- let name =
- (userinfo && (userinfo.name || userinfo.preferred_username || userinfo.nickname || userinfo.handle)) ||
- (claims && (claims.name || claims.preferred_username || claims.nickname || claims.handle));
- // 4) Fallback: use Codeforces public API with possible handle from claims.sub or claims.preferred_username
- if (!name && claims) {
- const possibleHandles = [];
- if (typeof claims.sub === 'string') possibleHandles.push(claims.sub);
- if (typeof claims.preferred_username === 'string') possibleHandles.push(claims.preferred_username);
- for (const h of possibleHandles) {
- try {
- const cf = await fetchJson(`https://codeforces.com/api/user.info?handles=${encodeURIComponent(h)}`);
- if (cf && cf.status === 'OK' && Array.isArray(cf.result) && cf.result[0]) {
- name = cf.result[0].handle || cf.result[0].firstName || cf.result[0].lastName;
- break;
- }
- } catch (e) {
- // ignore and continue
- }
- }
- }
- name = name || 'unknown';
- console.log('Logged in user:', name);
- return res.send(`Hello, ${name}`);
- } catch (err) {
- console.error('callback error', err);
- return res.status(500).send('Authentication failed');
- }
- });
- app.listen(PORT, () => {
- console.log(`Listening: http://localhost:${PORT}`);
- console.log(`Redirect URI (ensure registered in your app): ${REDIRECT_URI}`);
- });
Add Comment
Please, Sign In to add comment