Skip to content

Instantly share code, notes, and snippets.

@artursudnik
Created February 23, 2024 13:03
Show Gist options
  • Save artursudnik/f9e5925fb68730110037dfab1000e288 to your computer and use it in GitHub Desktop.
Save artursudnik/f9e5925fb68730110037dfab1000e288 to your computer and use it in GitHub Desktop.
OAuth Device Authorization Flow
import axios from 'axios';
import * as querystring from 'querystring';
const config: Record<string, string> = {
AUTH0_ISSUER: 'https://tenant.eu.auth0.com/',
AUTH0_AUDIENCE: '<your api id>',
AUTH0_CLIENT_ID: '<a client id>',
};
(async () => {
const deviceCode = await getDeviceCode({
issuer: config.AUTH0_ISSUER,
clientId: config.AUTH0_CLIENT_ID,
audience: config.AUTH0_AUDIENCE,
});
console.log(
`Please visit ${deviceCode.verification_uri_complete} and authorize this device.`,
);
console.log('WARNING: This will not work in a non-interactive environment!');
console.log(
'WARNING: if you are already logged in, you will not be prompted to log in again!',
);
console.log(
`If you are not sure, first visit this URL: ${config.AUTH0_ISSUER}oidc/logout/`,
);
const tokenResponse = await getToken({
issuer: config.AUTH0_ISSUER,
clientId: config.AUTH0_CLIENT_ID,
deviceCode: deviceCode.device_code,
interval: deviceCode.interval,
});
console.log(tokenResponse);
})();
type DeviceCodeResponse = {
device_code: string;
user_code: string;
verification_uri: string;
verification_uri_complete: string;
expires_in: number;
interval: number;
};
async function getDeviceCode(options: {
issuer: string;
clientId: string;
audience: string;
}): Promise<DeviceCodeResponse> {
const { issuer, clientId, audience } = options;
const response = await axios.post(
`${issuer}oauth/device/code`,
querystring.stringify({
client_id: clientId,
audience,
scope: 'openid profile email',
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
},
);
return response.data;
}
type TokenResponse = {
access_token: string;
token_type: string;
expires_in: number;
scope: string;
};
async function getToken(options: {
issuer: string;
clientId: string;
deviceCode: string;
interval: number;
}): Promise<TokenResponse> {
const { issuer, clientId, deviceCode, interval } = options;
// eslint-disable-next-line no-constant-condition
while (true) {
const response = await axios
.post(
`${issuer}oauth/token`,
querystring.stringify({
client_id: clientId,
device_code: deviceCode,
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
},
)
.catch((error) => {
if (
error.response.status === 403 &&
error.response.data.error === 'authorization_pending'
) {
console.log(error.response.data.error_description);
return error.response;
}
throw error;
});
if (response.status === 200) {
return response.data;
}
await new Promise((resolve) => setTimeout(resolve, interval * 1000));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment