AlertThis post is over a year old, some of this information may be out of date.
Create an Authentication Provider for Visual Studio Code
Previously I wrote how you could use the Microsoft Authentication Provider in your Visual Studio Code extension to custom Azure AD applications. In this article, we go a step further and create our authentication provider from scratch. As
By default, VS Code supports the github, github-enterprise, and microsoft authentication providers. If you use another service or have your authentication service, you will likely want to create your Authentication Provider.
There are two good references from which you can learn all of it:
constAUTH_TYPE=`auth0`;constAUTH_NAME=`Auth0`;constCLIENT_ID=`3GUryQ7ldAeKEuD2obYnppsnmj58eP5u`;constAUTH0_DOMAIN=`dev-txghew0y.us.auth0.com`;constSESSIONS_SECRET_KEY=`${AUTH_TYPE}.sessions`exportclassAuth0AuthenticationProviderimplementsAuthenticationProvider,Disposable{// Shortened for brevity
privateasynclogin(scopes: string[]=[]){returnawaitwindow.withProgress<string>({location: ProgressLocation.Notification,title:"Signing in to Auth0...",cancellable: true},async(_,token)=>{conststateId=uuid();this._pendingStates.push(stateId);if(!scopes.includes('openid')){scopes.push('openid');}if(!scopes.includes('profile')){scopes.push('profile');}if(!scopes.includes('email')){scopes.push('email');}constscopeString=scopes.join(' ');constsearchParams=newURLSearchParams([['response_type',"token"],['client_id',CLIENT_ID],['redirect_uri',this.redirectUri],['state',stateId],['scope',scopeString],['prompt',"login"]]);consturi=Uri.parse(`https://${AUTH0_DOMAIN}/authorize?${searchParams.toString()}`);awaitenv.openExternal(uri);letcodeExchangePromise=this._codeExchangePromises.get(scopeString);if(!codeExchangePromise){codeExchangePromise=promiseFromEvent(this._uriHandler.event,this.handleUri(scopes));this._codeExchangePromises.set(scopeString,codeExchangePromise);}try{returnawaitPromise.race([codeExchangePromise.promise,newPromise<string>((_,reject)=>setTimeout(()=>reject('Cancelled'),60000)),promiseFromEvent<any,any>(token.onCancellationRequested,(_,__,reject)=>{reject('User Cancelled');}).promise]);}finally{this._pendingStates=this._pendingStates.filter(n=>n!==stateId);codeExchangePromise?.cancel.fire();this._codeExchangePromises.delete(scopeString);}});}}
What is happening within the login method?
The login method does the following things:
Create a unique state ID and store it. The state ID gets verified after the sign-in;
Verify if the default permission scopes are added (openid, profile, and email);
Create the authorize URL with its required query string parameters; The redirect_uri is very important.
Open the authorize URL in your browser;
Wait for the token to come back, which gets handled in the handleUri method.
The redirect URI for VS Code and handling the redirect
To ensure your extension can receive a code/token, you will have to either use a localhost service or create a UriHandler. The UriHandler allows you to have a listener open your extension instance in VS Code.
classUriEventHandlerextendsEventEmitter<Uri>implementsUriHandler{publichandleUri(uri: Uri){this.fire(uri);}}exportclassAuth0AuthenticationProviderimplementsAuthenticationProvider,Disposable{constructor(privatereadonlycontext: ExtensionContext){this._disposable=Disposable.from(authentication.registerAuthenticationProvider(AUTH_TYPE,AUTH_NAME,this,{supportsMultipleAccounts: false}),window.registerUriHandler(this._uriHandler)// Register the URI handler
)}// Shortened for brevity
/**
* Handle the redirect to VS Code (after sign in from Auth0)
* @param scopes
* @returns
*/privatehandleUri:(scopes: readonlystring[])=>PromiseAdapter<Uri,string>=(scopes)=>async(uri,resolve,reject)=>{constquery=newURLSearchParams(uri.fragment);constaccess_token=query.get('access_token');conststate=query.get('state');if(!access_token){reject(newError('No token'));return;}if(!state){reject(newError('No state'));return;}// Check if it is a valid auth request started by the extension
if(!this._pendingStates.some(n=>n===state)){reject(newError('State not found'));return;}resolve(access_token);}}
How you handle the redirect depends on the authentication provider you are using. In the case of Auth0, the token and additional information like the state are provided as URI fragments.
For instance, if you use Azure AD auth, it will be provided as query string parameters.
The access token gets returned to the createSession method, and you have an authenticated session.
Get the current session
Now that creating a session is in place, it is time to complete the getSessions method.
VS Code calls this method when an extension uses the authentication.getSession. What we need to do here, is get the session data from the secret store and return the session if one exists.
constAUTH_TYPE=`auth0`;constSESSIONS_SECRET_KEY=`${AUTH_TYPE}.sessions`exportclassAuth0AuthenticationProviderimplementsAuthenticationProvider,Disposable{// Shortened for brevity
/**
* Get the existing sessions
* @param scopes
* @returns
*/publicasyncgetSessions(scopes?: string[]):Promise<readonlyAuthenticationSession[]>{constallSessions=awaitthis.context.secrets.get(SESSIONS_SECRET_KEY);if(allSessions){returnJSON.parse(allSessions)asAuthenticationSession[];}return[];}}
Removing a session
The removeSession method gets called when you sign out of the service for your extension. It will pass the sessionId to remove from your authenticated sessions.
All we need to do to use the authentication provider is add the following code to our extension:
1
2
3
4
constsession=awaitvscode.authentication.getSession("auth0",[],{createIfNone: false});if(session){vscode.window.showInformationMessage(`Welcome back ${session.account.label}`)}
The result looks as follows:
Remarks
The Auth0 authentication provider code is intended as an example to show what is needed to implement your custom authentication provider.
To make the provider complete, you best implement the following remarks:
Store the refresh token only, and when you initiate the extension, retrieve a new access token with the refresh token;
Validate if your refresh token is still valid. If not, remove the session.