Skip to content

Instantly share code, notes, and snippets.

@blubbel01
Last active March 22, 2024 02:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blubbel01/d3ff114356b1e3aebaf6f543fe0474ff to your computer and use it in GitHub Desktop.
Save blubbel01/d3ff114356b1e3aebaf6f543fe0474ff to your computer and use it in GitHub Desktop.
import express, {Express, Request, Response} from 'express';
import crypto from 'crypto';
import { ParsedQs } from "qs";
import axios from 'axios';
import jwt, { JwtPayload } from 'jsonwebtoken';
const APP_AUTH_URI = 'https://apiv1.vio-v.com/api/oauth2/authorize';
const APP_TOKEN_URI = 'https://apiv1.vio-v.com/api/oauth2/token';
const TOKEN_ISSUER = 'https://apiv1.vio-v.com'
// The data provided is only an example and SHALL be adjusted on use
const APP_REDIRECT_URI = 'https://example.com/login/callback';
const CLIENT_ID = 'T3zS5RvwTudXfJiVMektrQXAvVC2rTjidp7SEbtl';
const CLIENT_SECRET = ' 9s7WWoQM-1iYi-s1X8zSIlYPXeq7jV-mR5ToQ_fba5YKKcNr9O7cQGZPoBLzd4o1BcoRHtllqFksSgdqtCnzaas7u81TNryTmyBJ-LpZcq5ejaWx1v6FUOg98ca3KFpZtwTncufljMIzaEJ1RgovvHiELkidgBiU0jNj6s8t1-8 '; // only for non-public clients
declare module 'express-session' {
interface SessionData {
code_verifier?: string
state?: string
}
}
const app: Express = express();
interface ICallbackQuery extends ParsedQs {
code: string
state: string
iss: string // should be https://apiv1.vio-v.com
}
interface ITokenResponse {
access_token: string
token_type: string
expires_in: number
refresh_token: string
}
app.get('/login', (req: Request, res: Response) => {
// access granting the access_token and refresh_token to this client
const scopes = [
'read.self.info',
'read.group.info',
'read.group.members'
]
// PKCE Steps as defined in RFC 7636
const code_verifier = crypto.randomBytes(60).toString('ascii');
const code_challenge = crypto.createHash('SHA256').update(code_verifier).digest('base64url');
req.session.code_verifier = code_verifier;
const state = crypto.randomBytes(25).toString('base64url');
req.session.state = state;
const redirectUrl = new URL(APP_AUTH_URI);
redirectUrl.searchParams.append('code_challenge', code_challenge);
redirectUrl.searchParams.append('code_challenge_method', 'S256');
redirectUrl.searchParams.append('response_type', 'code');
redirectUrl.searchParams.append('client_id', CLIENT_ID);
redirectUrl.searchParams.append('scope', scopes.join(' '));
redirectUrl.searchParams.append('state', state);
redirectUrl.searchParams.append('redirect_uri', APP_REDIRECT_URI);
res.redirect(redirectUrl.toString());
});
app.get('/login/callback', async (req: Request, res: Response) => {
const {
code,
state,
iss,
error,
} = <ICallbackQuery> req.query;
if (req.session.code_verifier == null || req.session.state == null || iss !== TOKEN_ISSUER) {
res.sendStatus(500);
return;
}
if (state !== req.session.state) {
res.sendStatus(500);
return;
}
if(error != null) {
// Handle error
// error content is as defined in RFC 6749
return;
}
try {
const params = new URLSearchParams();
params.append('grant_type', 'authorization_code');
params.append('code', code);
params.append('code_verifier', req.session.code_verifier);
params.append('redirect_uri', APP_REDIRECT_URI);
params.append('client_id', CLIENT_ID);
params.append('client_secret', CLIENT_SECRET); // do not send if public client !!
const response = await axios.post<ITokenResponse>(APP_TOKEN_URI, params);
// save access_token and refresh_token somewhere
// detailed descreption in RFC 6749
} catch(_) {
// response status is not 200
res.sendStatus(500);
return;
}
});
/*
* Get valid access_token and if necessary regenerate using refresh_token
*/
async function getAccessToken(someLogic: any): Promise<string> {
// somehow get your refresh_token and access_token
const access_token = '';
const refresh_token = '';
const accessTokenData = jwt.decode(access_token, {
json: true
});
// ist token abgelaufen oder fehlerhaft
if (accessTokenData?.exp! < Math.floor(Date.now() / 1000) || accessTokenData == null) {
const params = new URLSearchParams();
params.append('grant_type', 'refresh_token');
params.append('refresh_token', refresh_token);
params.append('client_id', CLIENT_ID);
params.append('client_secret', CLIENT_SECRET); // do not send if public client !!
// this is optinal, only send if you want to limit the scopes on the access_token. can only grant scopes than already refresh_token has
// if not needed delete param scope and access_token will have same scope as refresh_token
params.append('scope', [
'read.self.info'
].join(' '));
const response = await axios.post<ITokenResponse>(APP_TOKEN_URI, params);
// save access_token and refresh_token somewhere
// you have to save refresh_token as the same refresh_token can not be used twice
// detailed descreption in RFC 6749
return response.data.access_token;
} else {
return access_token;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment