|
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)); |
|
} |
|
} |