Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication Provider API - consumer #100993

Closed
RMacfarlane opened this issue Jun 24, 2020 · 7 comments
Closed

Authentication Provider API - consumer #100993

RMacfarlane opened this issue Jun 24, 2020 · 7 comments
Assignees
Labels
api-finalization authentication Issues with the Authentication platform insiders-released Patch has been released in VS Code Insiders
Milestone

Comments

@RMacfarlane
Copy link
Contributor

#88309 introduces the concept of "Authentication Providers", which can be split into two types of extensions: those that implement the provider, and those that consume the provider. As part of the auth provider work, we are currently shipping two built in extensions that implement authentication for GitHub and Microsoft.

These two authentication providers have already been consumed by a number of partner extensions (GitHub Issues Notebook, GitHub Pull Requests, Codespaces, Azure Static Web Apps) and by settings sync. Now that the account management UI is present in stable, I'd like to drive more adoption of these two providers. I propose splitting out the "consumer" side of authentication providers and promoting this to stable, allowing other extensions to use GitHub and Microsoft authentication.

These pieces are:

export class AuthenticationSession {
	/**
	 * The identifier of the authentication session.
	 */
	readonly id: string;

	/**
	 * The access token.
	 */
	readonly accessToken: string;

	/**
	 * The account associated with the session.
	 */
	readonly account: {
		/**
		 * The human-readable name of the account.
		 */
		readonly displayName: string;

		/**
		 * The unique identifier of the account.
		 */
		readonly id: string;
	};

	/**
	 * The permissions granted by the session's access token. Available scopes
	 * are defined by the authentication provider.
	 */
	readonly scopes: string[];

	constructor(id: string, accessToken: string, account: { displayName: string, id: string }, scopes: string[]);
}

/**
 * An [event](#Event) which fires when an [AuthenticationProvider](#AuthenticationProvider) is added or removed.
 */
export interface AuthenticationProvidersChangeEvent {
	/**
	 * The ids of the [authenticationProvider](#AuthenticationProvider)s that have been added.
	 */
	readonly added: string[];

	/**
	 * The ids of the [authenticationProvider](#AuthenticationProvider)s that have been removed.
	 */
	readonly removed: string[];
}

/**
 * Options to be used when getting a session from an [AuthenticationProvider](#AuthenticationProvider).
 */
export interface AuthenticationGetSessionOptions {
	/**
	 *  Whether login should be performed if there is no matching session. Defaults to false.
	 */
	createIfNone?: boolean;

	/**
	 * Whether the existing user session preference should be cleared. Set to allow the user to switch accounts.
	 * Defaults to false.
	 */
	clearSessionPreference?: boolean;
}


/**
* An [event](#Event) which fires when an [AuthenticationSession](#AuthenticationSession) is added, removed, or changed.
*/
export interface AuthenticationSessionsChangeEvent {
	/**
	 * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been added.
	*/
	readonly added: string[];

	/**
	 * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been removed.
	 */
	readonly removed: string[];

	/**
	 * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been changed.
	 */
	readonly changed: string[];
}


export namespace authentication {
	/**
	 * Fires with the provider id that was registered or unregistered.
	 */
	export const onDidChangeAuthenticationProviders: Event<AuthenticationProvidersChangeEvent>;

	/**
	 * The ids of the currently registered authentication providers.
	 * @returns An array of the ids of authentication providers that are currently registered.
	 */
	export function getProviderIds(): Thenable<ReadonlyArray<string>>;

	/**
	 * Returns whether a provider has any sessions matching the requested scopes. This request
	 * is transparent to the user, no UI is shown. Rejects if a provider with providerId is not
	 * registered.
	 * @param providerId The id of the provider
	 * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication
	 * provider
	 * @returns A thenable that resolve to whether the provider has sessions with the requested scopes.
	 */
	export function hasSessions(providerId: string, scopes: string[]): Thenable<boolean>;

	/**
	 * Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not
	 * registered, or if the user does not consent to sharing authentication information with
	 * the extension. If there are multiple sessions with the same scopes, the user will be shown a
	 * quickpick to select which account they would like to use.
	 * @param providerId The id of the provider to use
	 * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
	 * @param options The [getSessionOptions](#GetSessionOptions) to use
	 * @returns A thenable that resolves to an authentication session
	 */
	export function getSession(providerId: string, scopes: string[], options: AuthenticationGetSessionOptions & { createIfNone: true }): Thenable<AuthenticationSession>;

	/**
	 * Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not
	 * registered, or if the user does not consent to sharing authentication information with
	 * the extension. If there are multiple sessions with the same scopes, the user will be shown a
	 * quickpick to select which account they would like to use.
	 * @param providerId The id of the provider to use
	 * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
	 * @param options The [getSessionOptions](#GetSessionOptions) to use
	 * @returns A thenable that resolves to an authentication session if available, or undefined if there are no sessions
	 */
	export function getSession(providerId: string, scopes: string[], options: AuthenticationGetSessionOptions): Thenable<AuthenticationSession | undefined>;

	/**
	* An [event](#Event) which fires when the array of sessions has changed, or data
	* within a session has changed for a provider. Fires with the ids of the providers
	* that have had session data change.
	*/
	export const onDidChangeSessions: Event<{ [providerId: string]: AuthenticationSessionsChangeEvent; }>;
}
@RMacfarlane RMacfarlane added api-finalization authentication Issues with the Authentication platform labels Jun 24, 2020
@RMacfarlane RMacfarlane added this to the June 2020 milestone Jun 24, 2020
@RMacfarlane RMacfarlane self-assigned this Jun 24, 2020
@warrenbuckley
Copy link

@RMacfarlane I would be very much interested in using the same GitHub auth in my own extension, without having to write my own to manage this & look forward to using this API 🎉

For me and my use case I am curious what the GitHub scope is please that is currently stored?
My current goal is to query the logged in user to see if they are a GitHub sponsor of a project.

Also when does this plan to be in stable VSCode so I can adopt/code for it in my extension.

Thanks
Warren :)

@jrieken
Copy link
Member

jrieken commented Jun 30, 2020

interface AuthenticationProviderInformation {
   readonly id: string;

   // future
   readonly displayName: string;
   readonly supportsMultipleAccounts: boolean;
}
  • arrays: readonly when possible
  • displayName -> label
  • extract AuthenticationSessionAccountInformation
  • auth provider info ⬆️
  • onDidChangeAuthenticationProviders, onDidChangeSessions use AuthenticationProviderInformation
  • getProviders() -> providers: AuthenticationProviderInformation

@RMacfarlane
Copy link
Contributor Author

@warrenbuckley I'm working on getting this into stable in our next release cycle, so within the next month.

When you sign in with GitHub for settings sync, it currently only requests the 'user:email' scope. However, using this API, you can request a new token with any scopes that you need.

@kieferrm kieferrm modified the milestones: June 2020, July 2020 Jul 6, 2020
RMacfarlane pushed a commit that referenced this issue Jul 6, 2020
gjsjohnmurray pushed a commit to gjsjohnmurray/vscode that referenced this issue Jul 8, 2020
gjsjohnmurray pushed a commit to gjsjohnmurray/vscode that referenced this issue Jul 9, 2020
@RMacfarlane
Copy link
Contributor Author

RMacfarlane commented Jul 14, 2020

export class AuthenticationSession {
	/**
	* The identifier of the authentication session.
	*/
	readonly id: string;
	
	/**
	* The access token.
	*/
	readonly accessToken: string;
	
	/**
	* The account associated with the session.
	*/
	readonly account: AuthenticationSessionAccountInformation;
	
	/**
	* The permissions granted by the session's access token. Available scopes
	* are defined by the authentication provider.
	*/
	readonly scopes: ReadonlyArray<string>;
	
	constructor(id: string, accessToken: string, account: AuthenticationSessionAccountInformation, scopes: string[]);
}

/**
* The information of an account associated with an authentication session.
*/
export interface AuthenticationSessionAccountInformation {
	/**
	* The unique identifier of the account.
	*/
	readonly id: string;
	
	/**
	* The human-readable name of the account.
	*/
	readonly label: string;
}

/**
* Basic information about an[authenticationProvider](#AuthenticationProvider)
*/
export interface AuthenticationProviderInformation {
	/**
	* The unique identifier of the authentication provider.
	*/
	readonly id: string;
	
	/**
	* The human-readable name of the authentication provider.
	*/
	readonly label: string;
}

/**
* An [event](#Event) which fires when an [AuthenticationProvider](#AuthenticationProvider) is added or removed.
*/
export interface AuthenticationProvidersChangeEvent {
	/**
	* The ids of the [authenticationProvider](#AuthenticationProvider)s that have been added.
	*/
	readonly added: ReadonlyArray<AuthenticationProviderInformation>;
	
	/**
	* The ids of the [authenticationProvider](#AuthenticationProvider)s that have been removed.
	*/
	readonly removed: ReadonlyArray<AuthenticationProviderInformation>;
}

/**
* Options to be used when getting a session from an [AuthenticationProvider](#AuthenticationProvider).
*/
export interface AuthenticationGetSessionOptions {
	/**
	*  Whether login should be performed if there is no matching session. Defaults to false.
	*/
	createIfNone?: boolean;
	
	/**
	* Whether the existing user session preference should be cleared. Set to allow the user to switch accounts.
	* Defaults to false.
	*/
	clearSessionPreference?: boolean;
}

export interface AuthenticationProviderAuthenticationSessionsChangeEvent extends AuthenticationSessionsChangeEvent {
	/**
	* The [authenticationProvider](#AuthenticationProvider) that has had its sessions change.
	*/
	readonly provider: AuthenticationProviderInformation;
}

/**
* An [event](#Event) which fires when an [AuthenticationSession](#AuthenticationSession) is added, removed, or changed.
*/
export interface AuthenticationSessionsChangeEvent {
	/**
	* The ids of the [AuthenticationSession](#AuthenticationSession)s that have been added.
	*/
	readonly added: ReadonlyArray<string>;
	
	/**
	* The ids of the [AuthenticationSession](#AuthenticationSession)s that have been removed.
	*/
	readonly removed: ReadonlyArray<string>;
	
	/**
	* The ids of the [AuthenticationSession](#AuthenticationSession)s that have been changed.
	*/
	readonly changed: ReadonlyArray<string>;
}

export namespace authentication {
	/**
	* Fires with the provider id that was registered or unregistered.
	*/
	export const onDidChangeAuthenticationProviders: Event<AuthenticationProvidersChangeEvent>;
	
	/**
	* An array of the information of authentication providers that are currently registered.
	*/
	export const providers: ReadonlyArray<AuthenticationProviderInformation>;
	
	/**
	* Returns whether a provider has any sessions matching the requested scopes. This request
	* is transparent to the user, no UI is shown. Rejects if a provider with providerId is not
	* registered.
	* @param providerId The id of the provider
	* @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication
	* provider
	* @returns A thenable that resolve to whether the provider has sessions with the requested scopes.
	*/
	export function hasSessions(providerId: string, scopes: string[]): Thenable<boolean>;
	
	/**
	* Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not
	* registered, or if the user does not consent to sharing authentication information with
	* the extension. If there are multiple sessions with the same scopes, the user will be shown a
	* quickpick to select which account they would like to use.
	* @param providerId The id of the provider to use
	* @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
	* @param options The [getSessionOptions](#GetSessionOptions) to use
	* @returns A thenable that resolves to an authentication session
	*/
	export function getSession(providerId: string, scopes: string[], options: AuthenticationGetSessionOptions & { createIfNone: true }): Thenable<AuthenticationSession>;
	
	/**
	* Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not
	* registered, or if the user does not consent to sharing authentication information with
	* the extension. If there are multiple sessions with the same scopes, the user will be shown a
	* quickpick to select which account they would like to use.
	* @param providerId The id of the provider to use
	* @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
	* @param options The [getSessionOptions](#GetSessionOptions) to use
	* @returns A thenable that resolves to an authentication session if available, or undefined if there are no sessions
	*/
	export function getSession(providerId: string, scopes: string[], options: AuthenticationGetSessionOptions): Thenable<AuthenticationSession | undefined>;
	
	/**
	* An [event](#Event) which fires when the array of sessions has changed, or data
	* within a session has changed for a provider. Fires with the ids of the providers
	* that have had session data change.
	*/
	export const onDidChangeSessions: Event<AuthenticationProviderAuthenticationSessionsChangeEvent>;
}

@RMacfarlane
Copy link
Contributor Author

RMacfarlane commented Jul 21, 2020

Since last week

  • AuthenticationSession is now an interface
  • hasSessions has been removed
  • The sessionChange event now just has providerId
  • When getSesssion is called, an activation event is sent to the provider
export interface AuthenticationSession {
	/**
	* The identifier of the authentication session.
	*/
	readonly id: string;
	
	/**
	* The access token.
	*/
	readonly accessToken: string;
	
	/**
	* The account associated with the session.
	*/
	readonly account: AuthenticationSessionAccountInformation;
	
	/**
	* The permissions granted by the session's access token. Available scopes
	* are defined by the authentication provider.
	*/
	readonly scopes: ReadonlyArray<string>;
}

/**
* The information of an account associated with an authentication session.
*/
export interface AuthenticationSessionAccountInformation {
	/**
	* The unique identifier of the account.
	*/
	readonly id: string;
	
	/**
	* The human-readable name of the account.
	*/
	readonly label: string;
}

/**
* Basic information about an[authenticationProvider](#AuthenticationProvider)
*/
export interface AuthenticationProviderInformation {
	/**
	* The unique identifier of the authentication provider.
	*/
	readonly id: string;
	
	/**
	* The human-readable name of the authentication provider.
	*/
	readonly label: string;
}

/**
* An [event](#Event) which fires when an [AuthenticationProvider](#AuthenticationProvider) is added or removed.
*/
export interface AuthenticationProvidersChangeEvent {
	/**
	* The ids of the [authenticationProvider](#AuthenticationProvider)s that have been added.
	*/
	readonly added: ReadonlyArray<AuthenticationProviderInformation>;
	
	/**
	* The ids of the [authenticationProvider](#AuthenticationProvider)s that have been removed.
	*/
	readonly removed: ReadonlyArray<AuthenticationProviderInformation>;
}

/**
* Options to be used when getting a session from an [AuthenticationProvider](#AuthenticationProvider).
*/
export interface AuthenticationGetSessionOptions {
	/**
	*  Whether login should be performed if there is no matching session. Defaults to false.
	*/
	createIfNone?: boolean;
	
	/**
	* Whether the existing user session preference should be cleared. Set to allow the user to switch accounts.
	* Defaults to false.
	*/
	clearSessionPreference?: boolean;
}

export interface AuthenticationProviderAuthenticationSessionsChangeEvent {
	/**
	* The [authenticationProvider](#AuthenticationProvider) that has had its sessions change.
	*/
	readonly provider: AuthenticationProviderInformation;
}

export namespace authentication {
	
	/**
	* Fires with the provider id that was registered or unregistered.
	*/
	export const onDidChangeAuthenticationProviders: Event<AuthenticationProvidersChangeEvent>;
	
	/**
	* An array of the information of authentication providers that are currently registered.
	*/
	export const providers: ReadonlyArray<AuthenticationProviderInformation>;
	
	/**
	* Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not
	* registered, or if the user does not consent to sharing authentication information with
	* the extension. If there are multiple sessions with the same scopes, the user will be shown a
	* quickpick to select which account they would like to use.
	* @param providerId The id of the provider to use
	* @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
	* @param options The [getSessionOptions](#GetSessionOptions) to use
	* @returns A thenable that resolves to an authentication session
	*/
	export function getSession(providerId: string, scopes: string[], options: AuthenticationGetSessionOptions & { createIfNone: true }): Thenable<AuthenticationSession>;
	
	/**
	* Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not
	* registered, or if the user does not consent to sharing authentication information with
	* the extension. If there are multiple sessions with the same scopes, the user will be shown a
	* quickpick to select which account they would like to use.
	* @param providerId The id of the provider to use
	* @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
	* @param options The [getSessionOptions](#GetSessionOptions) to use
	* @returns A thenable that resolves to an authentication session if available, or undefined if there are no sessions
	*/
	export function getSession(providerId: string, scopes: string[], options: AuthenticationGetSessionOptions): Thenable<AuthenticationSession | undefined>;
	
	
	/**
	* An [event](#Event) which fires when the array of sessions has changed, or data
	* within a session has changed for a provider. Fires with the ids of the providers
	* that have had session data change.
	*/
	export const onDidChangeSessions: Event<AuthenticationProviderAuthenticationSessionsChangeEvent>;
}

@RMacfarlane
Copy link
Contributor Author

RMacfarlane commented Jul 28, 2020

Currently in stable is the following:

	export interface AuthenticationSession {
		/**
		 * The identifier of the authentication session.
		 */
		readonly id: string;

		/**
		 * The access token.
		 */
		readonly accessToken: string;

		/**
		 * The account associated with the session.
		 */
		readonly account: AuthenticationSessionAccountInformation;

		/**
		 * The permissions granted by the session's access token. Available scopes
		 * are defined by the [AuthenticationProvider](#AuthenticationProvider).
		 */
		readonly scopes: ReadonlyArray<string>;
	}

	/**
	 * The information of an account associated with an [AuthenticationSession](#AuthenticationSession).
	 */
	export interface AuthenticationSessionAccountInformation {
		/**
		 * The unique identifier of the account.
		 */
		readonly id: string;

		/**
		 * The human-readable name of the account.
		 */
		readonly label: string;
	}


	/**
	 * Options to be used when getting an [AuthenticationSession](#AuthenticationSession) from an [AuthenticationProvider](#AuthenticationProvider).
	 */
	export interface AuthenticationGetSessionOptions {
		/**
		 *  Whether login should be performed if there is no matching session. Defaults to false.
		 */
		createIfNone?: boolean;

		/**
		 * Whether the existing user session preference should be cleared. Set to allow the user to switch accounts.
		 * Defaults to false.
		 */
		clearSessionPreference?: boolean;
	}

	/**
	 * Basic information about an[authenticationProvider](#AuthenticationProvider)
	 */
	export interface AuthenticationProviderInformation {
		/**
		 * The unique identifier of the authentication provider.
		 */
		readonly id: string;

		/**
		 * The human-readable name of the authentication provider.
		 */
		readonly label: string;
	}

	/**
	 * An [event](#Event) which fires when an [AuthenticationSession](#AuthenticationSession) is added, removed, or changed.
	 */
	export interface AuthenticationSessionsChangeEvent {
		/**
		 * The [authenticationProvider](#AuthenticationProvider) that has had its sessions change.
		 */
		readonly provider: AuthenticationProviderInformation;
	}

	export namespace authentication {
		/**
		 * Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not
		 * registered, or if the user does not consent to sharing authentication information with
		 * the extension. If there are multiple sessions with the same scopes, the user will be shown a
		 * quickpick to select which account they would like to use.
		 * @param providerId The id of the provider to use
		 * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
		 * @param options The [getSessionOptions](#GetSessionOptions) to use
		 * @returns A thenable that resolves to an authentication session
		 */
		export function getSession(providerId: string, scopes: string[], options: AuthenticationGetSessionOptions & { createIfNone: true }): Thenable<AuthenticationSession>;

		/**
		 * Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not
		 * registered, or if the user does not consent to sharing authentication information with
		 * the extension. If there are multiple sessions with the same scopes, the user will be shown a
		 * quickpick to select which account they would like to use.
		 * @param providerId The id of the provider to use
		 * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider
		 * @param options The [getSessionOptions](#GetSessionOptions) to use
		 * @returns A thenable that resolves to an authentication session if available, or undefined if there are no sessions
		 */
		export function getSession(providerId: string, scopes: string[], options: AuthenticationGetSessionOptions): Thenable<AuthenticationSession | undefined>;

		/**
		 * An [event](#Event) which fires when the array of sessions has changed, or data
		 * within a session has changed for a provider. Fires with the ids of the providers
		 * that have had session data change.
		 */
		export const onDidChangeSessions: Event<AuthenticationProviderAuthenticationSessionsChangeEvent>;
	}

The one piece that did not make it in from above is providers and onDidChangeAuthenticationProviders. My hope was to get rid of the need for onDidChangeAuthenticationProviders by having auth provider extensions statically register themselves so that providers could be accurate at startup. However, I haven't found a way to implement this with providers being sync - resolving contribution points from package.json is async (and also currently things handling this live in the renderer), so the ext host piece would need to block on this.

One option is to leave it as it currently is, which means that consumers would have to subscribe to onDidChangeAuthenticationProviders to see if the provider they're looking for is registered. Another is to make providers back into getProviders, an async function, which would eliminate the need for static registration and could activate the auth provider extension in the same way that getSession does.

I'll create a new issue to discuss what to do with these properties, but having getSession by itself unblocks many use cases.

@Domiii
Copy link

Domiii commented Aug 6, 2020

Thanks @RMacfarlane, this is great stuff!

Two questions:

  1. There does not seem to be a way to logout?
  2. How would one access the data provided by additional scopes, such as email?

@github-actions github-actions bot locked and limited conversation to collaborators Sep 11, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-finalization authentication Issues with the Authentication platform insiders-released Patch has been released in VS Code Insiders
Projects
None yet
Development

No branches or pull requests

6 participants
@Domiii @warrenbuckley @jrieken @RMacfarlane @kieferrm and others