Skip to content

Commit

Permalink
Allow any responseType (#88)
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Oberwasserlechner authored Apr 20, 2020
1 parent 7804b1c commit f376de5
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 52 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
## [Unreleased]

### Breaking
* Core: Capacitor 2.x is new minimum peer dependency. closes #80
* Core: Capacitor 2.x is new minimum peer dependency. closes #80.
* `responseType` is required. Default values were removed. In favor of configuring anything. closes #86.
* Configuration: If a flow must not have a `accessTokenEndpoint` but you configured one as base parameter you have to
overwrite it in the according platform sections. `accessTokenEndpoint: ""` see Google example in README.
* Add `redirectUrl` to base parameter and make it overwritable in the platform sections. closes #84.
Expand All @@ -23,6 +24,7 @@ This is controlled by Android specific parameters `handleResultOnNewIntent` for
### Changed
* Android: Allow no resource url and just return every we got until so far. closes #75. thx [@0x4AMiller](https://github.com/0x4AMiller)
* Web, iOS, Android: All base parameters are overwritable in the platform sections. closes #84.
* Restriction to the response type `code` and `token` was removed. Devs can configure anything but are responsible for it as well. closes #86.

### Fixed

Expand Down
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,24 +154,32 @@ But be aware that only the parameters from the accessToken request are included

### Error Codes

#### authenticate()

* ERR_PARAM_NO_APP_ID ... The appId / clientId is missing. (web, android, ios)
* ERR_PARAM_NO_AUTHORIZATION_BASE_URL ... The authorization base url is missing. (web, android, ios)
* ERR_PARAM_NO_RESPONSE_TYPE ... The response type is missing. (web, android, ios)
* ERR_PARAM_NO_REDIRECT_URL ... The redirect url / custom scheme url is missing. (web, android, ios)
* ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT ... The access token endpoint url is missing. It is only needed if code flow is used. (web, android, ios)
* ERR_PARAM_INVALID_RESPONSE_TYPE ... You configured a invalid responseType. Only "token" or "code" are allowed. (web, android, ios)
* ERR_PARAM_NO_REFRESH_TOKEN ... The refresh token is missing (only when obtaining an access token based on a refresh token, android/ios)
* ERR_PARAM_NO_REDIRECT_URL ... The redirect url is missing. (web, android, ios)

* ERR_STATES_NOT_MATCH ... The state included in the authorization code request does not match the one in the redirect. Security risk! (web, android, ios)
* ERR_AUTHORIZATION_FAILED ... The authorization failed.
* ERR_NO_ACCESS_TOKEN ... No access_token found. (web, android)
* ERR_NO_AUTHORIZATION_CODE ... No authorization code was returned in the redirect response. (web, android, ios)
* ERR_STATES_NOT_MATCH ... The state included in the authorization code request does not match the one in the redirect. Security risk! (web, android, ios)
* USER_CANCELLED ... The user cancelled the login flow. (web, android, ios)
* ERR_CUSTOM_HANDLER_LOGIN ... Login through custom handler class failed. See logs and check your code. (android, ios)
* ERR_CUSTOM_HANDLER_LOGOUT ... Logout through custom handler class failed. See logs and check your code. (android, ios)
* ERR_ANDROID_NO_BROWSER ... No suitable browser could be found! (Android)
* ERR_ANDROID_RESULT_NULL ... The auth result is null. The intent in the ActivityResult is null. This might be a valid state but make sure you configured Android part correctly! See [Platform Android](#platform-android)
* ERR_GENERAL ... A unspecific error. Check the logs to see want exactly happened. (web, android, ios)

#### refreshToken()

* ERR_PARAM_NO_APP_ID ... The appId / clientId is missing. (android, ios)
* ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT ... The access token endpoint url is missing. It is only needed on refresh, on authenticate it is optional. (android, ios)
* ERR_PARAM_NO_REFRESH_TOKEN ... The refresh token is missing. (android, ios)
* ERR_NO_ACCESS_TOKEN ... No access_token found. (web, android)
* ERR_GENERAL ... A unspecific error. Check the logs to see want exactly happened. (android, ios)

## Platform: Web/PWA

This implementation just opens a browser window to let users enter their credentials.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ public class OAuth2ClientPlugin extends Plugin {
private static final String ERR_PARAM_NO_REDIRECT_URL = "ERR_PARAM_NO_REDIRECT_URL";
private static final String ERR_PARAM_NO_RESPONSE_TYPE = "ERR_PARAM_NO_RESPONSE_TYPE";

private static final String ERR_PARAM_INVALID_RESPONSE_TYPE = "ERR_PARAM_INVALID_RESPONSE_TYPE";

private static final String ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT = "ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT";
private static final String ERR_PARAM_NO_REFRESH_TOKEN = "ERR_PARAM_NO_REFRESH_TOKEN";

Expand Down Expand Up @@ -178,6 +176,11 @@ public void onError(Exception error) {
call.reject(ERR_GENERAL, e);
}
} else {

// ###################################
// ### Validate required parameter ###
// ###################################

if (oauth2Options.getAppId() == null) {
call.reject(ERR_PARAM_NO_APP_ID);
return;
Expand All @@ -198,17 +201,7 @@ public void onError(Exception error) {
return;
}

// TODO remove check #46, #48, #49
if (!RESPONSE_TYPE_CODE.equals(oauth2Options.getResponseType()) && !RESPONSE_TYPE_TOKEN.equals(oauth2Options.getResponseType())) {
call.reject(ERR_PARAM_INVALID_RESPONSE_TYPE);
return;
}

// TODO remove check. If there is a accessTokenEndpoint given the plugin will try it regardless of the given responseType
if (RESPONSE_TYPE_CODE.equals(oauth2Options.getResponseType()) && oauth2Options.getAccessTokenEndpoint() == null) {
call.reject(ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT);
return;
}
// ### Configure

Uri authorizationUri = Uri.parse(oauth2Options.getAuthorizationBaseUrl());
Uri accessTokenUri;
Expand Down
28 changes: 5 additions & 23 deletions ios/ByteowlsCapacitorOauth2/Source/ByteowlsCapacitorOauth2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ public class OAuth2ClientPlugin: CAPPlugin {
let PARAM_CUSTOM_HANDLER_CLASS = "ios.customHandlerClass"
let PARAM_SCOPE = "scope"
let PARAM_STATE = "state"
let PARAM_PKCE_DISABLED = "pkceDisabled"

let RESPONSE_TYPE_CODE = "code"
let RESPONSE_TYPE_TOKEN = "token"
let PARAM_PKCE_ENABLED = "pkceEnabled"

let ERR_GENERAL = "ERR_GENERAL"

Expand All @@ -44,8 +41,6 @@ public class OAuth2ClientPlugin: CAPPlugin {
let ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT = "ERR_PARAM_NO_ACCESS_TOKEN_ENDPOINT"
let ERR_NO_AUTHORIZATION_CODE = "ERR_NO_AUTHORIZATION_CODE"
let ERR_PARAM_NO_REFRESH_TOKEN = "ERR_PARAM_NO_REFRESH_TOKEN"

let ERR_PARAM_INVALID_RESPONSE_TYPE = "ERR_PARAM_INVALID_RESPONSE_TYPE"

struct SharedConstants {
static let ERR_USER_CANCELLED = "USER_CANCELLED"
Expand Down Expand Up @@ -116,7 +111,7 @@ public class OAuth2ClientPlugin: CAPPlugin {
consumerSecret: "", // never ever store the app secret on client!
authorizeUrl: "",
accessTokenUrl: accessTokenEndpoint,
responseType: RESPONSE_TYPE_CODE
responseType: "code"
)

self.oauthSwift = oauthSwift
Expand Down Expand Up @@ -224,10 +219,6 @@ public class OAuth2ClientPlugin: CAPPlugin {
return
}

if responseType != RESPONSE_TYPE_CODE && responseType != RESPONSE_TYPE_TOKEN {
call.reject(self.ERR_PARAM_INVALID_RESPONSE_TYPE)
return
}

var oauthSwift: OAuth2Swift
if let accessTokenEndpoint = getOverwritableString(call, PARAM_ACCESS_TOKEN_ENDPOINT), !accessTokenEndpoint.isEmpty {
Expand Down Expand Up @@ -267,10 +258,9 @@ public class OAuth2ClientPlugin: CAPPlugin {
}

let requestState = getOverwritableString(call, PARAM_STATE) ?? generateRandom(withLength: 20)
let pkceDisabled: Bool = getOverwritable(call, PARAM_PKCE_DISABLED) as? Bool ?? false
let pkceEnabled: Bool = getOverwritable(call, PARAM_PKCE_ENABLED) as? Bool ?? false
// if response type is code and pkce is not disabled
if responseType == RESPONSE_TYPE_CODE && !pkceDisabled {
// oauthSwift.accessTokenBasicAuthentification = true
if pkceEnabled {
let pkceCodeVerifier = generateRandom(withLength: 64)
let pkceCodeChallenge = pkceCodeVerifier.sha256().base64()

Expand Down Expand Up @@ -326,14 +316,7 @@ public class OAuth2ClientPlugin: CAPPlugin {
private func handleAuthorizationResult(_ result: Result<OAuthSwift.TokenSuccess, OAuthSwiftError>, _ call: CAPPluginCall, _ responseType: String, _ requestState: String, _ resourceUrl: String?) {
switch result {
case .success(let (credential, response, parameters)):
// oauthSwift internally checks the state if response type is code therefore I only need the token check
if responseType == self.RESPONSE_TYPE_TOKEN {
guard let responseState = parameters["state"] as? String, responseState == requestState else {
call.reject(self.ERR_STATES_NOT_MATCH)
return
}
}

// state is aready checked by the lib
if resourceUrl != nil && !resourceUrl!.isEmpty {
self.oauthSwift!.client.get(
resourceUrl!,
Expand Down Expand Up @@ -375,7 +358,6 @@ public class OAuth2ClientPlugin: CAPPlugin {
}
}


private func getConfigObjectDeepest(_ options: [AnyHashable: Any?]!, key: String) -> [AnyHashable:Any?]? {
let parts = key.split(separator: ".")

Expand Down
15 changes: 10 additions & 5 deletions src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,31 @@ export interface OAuth2RefreshTokenOptions {
scope?: string;
}

type ResponseTypeType = "token" | "code";

export interface OAuth2AuthenticateBaseOptions {
/**
* The app id (client id) you get from the oauth provider like Google, Facebook,...
*
* required!
*/
appId?: string;
/**
* The base url for retrieving tokens depending on the response type from a OAuth 2 provider. e.g. https://accounts.google.com/o/oauth2/auth
*
* required!
*/
authorizationBaseUrl?: string;
/**
* Defaults to 'token' aka implicit flow if empty.
* Tells the authorization server which grant to execute. Be aware that a full code flow is not supported as clientCredentials are not included in requests.
*
* Be aware that this plugin does not support authorization code flow with client secrets because of security reason.
* But you can retrieve the authorizationCode if you don't set a accessTokenEndpoint.
*
* required!
*/
responseType?: ResponseTypeType
responseType?: string;
/**
* Url to which the oauth provider redirects after authentication.
*
* required!
*/
redirectUrl?: string;
/**
Expand Down
10 changes: 5 additions & 5 deletions src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ export class OAuth2ClientPluginWeb extends WebPlugin implements OAuth2ClientPlug
this.webOptions = await WebUtils.buildWebOptions(options);
return new Promise<any>((resolve, reject) => {
// validate
if (!this.webOptions.appId) {
if (!this.webOptions.appId || this.webOptions.appId.length == 0) {
reject(new Error("ERR_PARAM_NO_APP_ID"));
} else if (!this.webOptions.authorizationBaseUrl) {
} else if (!this.webOptions.authorizationBaseUrl || this.webOptions.authorizationBaseUrl.length == 0) {
reject(new Error("ERR_PARAM_NO_AUTHORIZATION_BASE_URL"));
} else if (!this.webOptions.redirectUrl) {
} else if (!this.webOptions.redirectUrl || this.webOptions.redirectUrl.length == 0) {
reject(new Error("ERR_PARAM_NO_REDIRECT_URL"));
} else if ("code" !== this.webOptions.responseType && "token" !== this.webOptions.responseType) {
reject(new Error("ERR_PARAM_INVALID_RESPONSE_TYPE"));
} else if (!this.webOptions.responseType || this.webOptions.responseType.length == 0) {
reject(new Error("ERR_PARAM_NO_RESPONSE_TYPE"));
} else {
// init internal control params
let loopCount = this.loopCount;
Expand Down

0 comments on commit f376de5

Please sign in to comment.