-
Notifications
You must be signed in to change notification settings - Fork 162
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
AppAuth-JS in combination with WebExtensions #8
Comments
We don't know a lot about WebExtensions and their restrictions, unfortunately. We're focusing on the native app cases like using Electron / NW.js for now. The library itself will let you send whatever authorization request parameters you like, so you could conceivably run a resource owner flow within your extension. In the more general case where you'd like to be able to handle a redirect URI for the authorization response, registering a custom scheme may work (though support looks pretty patchy). Sorry, we don't have a lot of bandwidth to help with this right now, but we'd be interested to hear what you eventually manage to get working if you continue with this approach. |
Hi. Thanks for the question. I would love to help as one of the goals of the library was to make it possible to work with Chrome and web extensions. If you need better support for WebExtensions, please reach out. In the meantime I am going to take a look at what the capabilities are for a web extension. |
Hi Tikurahul, Therefore I'm looking for an alternative way where no separate browser window for the authentication flow is needed. Thanks again. |
You might be able to use a request rewriter for handling the redirect. It's like a foreign fetch to your extension. (https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Intercept_HTTP_requests). The redirect URI can contain a dynamic unique path so as to not clash with a real request. WDYT? |
So, if I want to use the resource-owner flow. Can you help me get started? From the existing documentation of AppAuth-JS I find it a bit difficult where to start/ |
Sorry, that things are a little hard to discover. At the moment, AppAuth-JS does not expose any higher level APIs to make the flow very easy. However, you should still be able to make your requests work. Take a look at index.ts which exposes these primitives. https://github.com/openid/AppAuth-JS/blob/master/src/app/index.ts#L87 is an optional openid configuration discovery request. If you don't know the authorization endpoints and the token endpoints you can just specify a provider URL for the library to auto-discover these endpoints. https://github.com/openid/AppAuth-JS/blob/master/src/app/index.ts#L100 makes the actual authorization request. This is where you will need to specify the The Once you get the If you want to see all of this running on a page that you can test things on, take a look at (https://github.com/openid/AppAuth-JS/blob/master/app/index.html). Look at the https://github.com/openid/AppAuth-JS#development-workflow to set up the package for testing, and run the npm script Notes on importing classesThis library uses TypeScript which is what I have linked to. I am also publishing the compiled javascript in the So imports in
Should turn into something like:
-- Please let me know if you have more questions. |
Hi Rahul, My main question remains, though. I need to figure out whether it is possible to have an authentication flow that can run completely in the single popup window of my browser extension. A redirect to a login page of an openid-connect login provider doesn't work in this main popup window (afaik). Ans, as I explained, opening a second browser window from my plugin to execute the authentication flow is not portable across browser. My understanding is that I then should use the resource-owner flow where I can pass the username and password directly to my identity server, such that no redirect is needed. Do you agree, or is there an alternative? Despite your explanation, I don't know where to start with getting the resource-owner flow to work, such I can pass username/password directly without a redirect. Any tips/suggestions? Thanks again. |
You should be able to use the standard client redirect flow, where you can have your extension handle the client redirect locally, using the https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Intercept_HTTP_requests technique that I talked about. See if that technique works. Otherwise, you can run service workers + foreign fetch. |
Thanks again for your help. Sorry if I misunderstand you. I think I understand how I can use the Intercept_HTTP_requests to get authentication tokens from the redirected url. However, I do not understand how it would help me to bypass the login screen from the identity server. In other words, how would it help me to pass username/password to the identity server? I'm not (yet) familiar with service workers + foreign fetch. |
I am not sure if I understand your question. Given that your extension can handle the redirect, you should be able to capture the authorization code via the redirect - and pass it on to the In your case, I think you want support for a |
Yes, I think what I'm looking for (but I'm not an expert) is the Resource owner password grant type (http://docs.identityserver.io/en/release/topics/grant_types.html). My goal is not to redirect my browser extension to a login page of the identity provider because that requires that my extension opens another browser window which results in the earlier explained issues. Rather, my extension should offer the user it's own login screen where the user can enter his/her username/password. The extension then submits the username/password to the identity provider and returns authentication tokens on return. I hope this makes clear what I want. If you can point me in the right direction to achieve this, I would be very happy! |
So one of the explicit goals of AppAuth-Js is to not require the user to enter their credentials again, and to re-use the IDP's cookie jar or autofill as much as we can. We can only do that when when we redirect to the IDP's login page. For It should be really easy to add support for The result of the authorization request should give you a For guidance on the request you need to make you can take a look at: Once you have the |
Thanks again! I will certainly give it a try and I'll keep you updated. |
Did you manage to get the |
I'm sorry no time yet :-( |
Hi Tikurahul, Merijn |
It turns out I have to write my own version of buildRequestUrl because the existing one has hard-coded elements that are added to the request url (see requestMap). In particular it always adds a redirect_uri. Moreover, a url is created with a query string which is then assigned to the location of the browser to trigger the redirect. This is done with the statement: this.locationLike.assign(url); For ResourceOwnerPassword flow to work, I think the parameters should be posted to the server using an HTML post method. Is this provided in the AppAuth library? If not, could you give me an advise how I could best add this? |
You are correct. For the 2nd problem, currently the |
Thanks. What would be the best way to make an XHR? |
You can use https://developer.mozilla.org/en/docs/Web/API/Fetch_API or https://github.com/openid/AppAuth-JS/blob/master/src/xhr.ts#L25 if you happen to be bundling |
Thanks again for answering my questions. |
Unless you happen to be using OpenID connect service discovery or need to do token exchange, I would say using AppAuth in this particular use case does not get you much. All you need to so is to make an XHR for the |
Service discovery is also not a big deal, right? |
Yes, in your case if all you care about is an |
Can i go ahead and mark this question answered ? |
Marking the question as answered. |
Sorry, for not responding earlier. import { Injectable, Inject } from '@angular/core';
import { Headers, Http, URLSearchParams, Response } from '@angular/http';
import 'rxjs/add/operator/toPromise';
export abstract class IAuthConfig {
clientId: string;
clientSecret: string;
openIdConnectUrl: string;
scope: string;
response_type: string;
}
export const OidcConfig: IAuthConfig = {
clientId: '',
clientSecret: '',
openIdConnectUrl: '',
scope: 'openid ...',
response_type: 'id_token'
};
// Interface representing a user identity in the app
interface IUserIdentity {
email: string;
token: string;
expires_in: number;
expires_at: number;
}
// Interface representing use info as return from userinfo_endpoint
interface UserInfo {
// properties from the userinfo_endpoint
}
interface OidcTokenResponse {
access_token: string
expires_in: number
token_type: string
}
interface OidcServiceConfiguration {
authorization_endpoint: string
token_endpoint: string
userinfo_endpoint: string
end_session_endpoint: string
}
interface MindYourPassUserInfo {
email: string;
MindYourPassUserSalt : string;
}
export abstract class IOpenIdAuthenticationService {
protected readonly wellKnownPath: string = '.well-known';
protected readonly openIdCOnfiguration: string = 'openid-configuration';
protected readonly userName: string = "username";
protected readonly password: string = "password";
protected readonly clientId: string = "client_id";
protected readonly grantType: string = "grant_type";
protected readonly grantTypePassword: string = "password";
abstract async login(userName: string, password: string): Promise<IUserIdentity>;
abstract async logout(userIdentity: IUserIdentity): Promise<Response>;
}
@Injectable()
export class OpenIdAuthenticationService extends IOpenIdAuthenticationService {
constructor(
private http: Http,
@Inject(AuthConfig) private config: IAuthConfig) {
super();
}
async login(userName: string, password: string): Promise<IUserIdentity> {
var serviceConfiguration = await this.getServiceConfigurationFromServer();
var tokenResponse = await this.authorizeUser(serviceConfiguration, userName, password);
var userInfo = await this.getUserInfo(serviceConfiguration, tokenResponse.access_token);
let now = Date.now() / 1000;
var userIdentity: IUserIdentity = {
email: userInfo.email,
token: tokenResponse.access_token,
expires_in: tokenResponse.expires_in,
expires_at: now + tokenResponse.expires_in
};
return userIdentity;
}
async logout(userIdentity: IUserIdentity): Promise<Response> {
var serviceConfiguration = await this.getServiceConfigurationFromServer();
var url = serviceConfiguration.end_session_endpoint;
let query = new URLSearchParams();
query.append('id_token_hint', userIdentity.token);
var headers = new Headers(
{
'Content-Type': 'application/x-www-form-urlencoded',
});
return await this.http
.get(`${url}?${query}`, { headers: headers })
.toPromise();
}
private async getUserInfo(
serviceConfiguration: OidcServiceConfiguration,
accessToken: string): Promise<MindYourPassUserInfo> {
var url = serviceConfiguration.userinfo_endpoint;
var headers = new Headers(
{
'Authorization': `Bearer ${accessToken}`
});
var response = await this.http.get(url, { headers: headers }).toPromise();
return response.json() as MindYourPassUserInfo;
}
private async authorizeUser(serviceConfiguration: OidcServiceConfiguration,
userName: string, password: string): Promise<OidcTokenResponse> {
var url = `${serviceConfiguration.token_endpoint}`;
let postData = new URLSearchParams();
postData.append('grant_type', 'password');
postData.append('username', userName);
postData.append('password', password);
postData.append('scope', this.config.scope);
postData.append('response_type', this.config.response_type);
var authorizationHeader = btoa(`${this.config.clientId}:${this.config.clientSecret}`);
var headers = new Headers(
{
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${authorizationHeader}`
}
);
var response = await this.http
.post(url, postData, { headers: headers })
.toPromise();
return response.json();
}
private async getServiceConfigurationFromServer(): Promise<OidcServiceConfiguration> {
var url = `${this.config.openIdConnectUrl}/${this.wellKnownPath}/${this.openIdCOnfiguration}`;
var result = await this.http.get(url)
.toPromise();
return result.json() as OidcServiceConfiguration;
}
} |
Hi,
I'm developing a web browser extension (using WebExtensions standard) in which a user needs to authenticate using openid-connect. I'm facing all sorts of issues with redirects as part of the authentication flow. The main reason is that I have to open an addition browser (popup) window where the authentication flow is executed. This is because redirects are not supported in the main popup of a browser extension. Now, several browsers will simply close the main popup window if it looses focus, which happens when the authentication window is opened :-(
I tried hard to get oidc-client to do the job, but it is not satisfactory.
My question is, will AppAuth-JS help me by supporting (e.g.) the resource-owner flow, such that I can simply pass username/password to my identity provider? Or is there another preferred approach to get openid-connect authentication work in a browser extension? Are there any examples? Is someone willing to help?
Thanks in advance.
The text was updated successfully, but these errors were encountered: