Skip to content

Commit

Permalink
BREAKING - automatic credential management (#6)
Browse files Browse the repository at this point in the history
* BREAKING - automatic credential management

* code refactoring

* removed unused argument

* fixed arguments order + updated README)

* added a line in README
  • Loading branch information
TheFe91 authored Oct 1, 2020
1 parent e976f95 commit 882dc84
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 36 deletions.
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ yarn add react-native-keycloak-plugin

### App configuration

Please configure [Linking](https://facebook.github.io/react-native/docs/linking.html) module, including steps for handling Universal links (This might get changed due to not being able to close the tab on leave, ending up with a lot of tabs in the browser).
Please configure [Linking](https://facebook.github.io/react-native/docs/linking.html) module, including steps for handling Universal links<br>
This might get changed due to not being able to close the tab on leave, ending up with a lot of tabs in the browser.<br>
_[Not needed if you're using React Native >= 0.60]_

Also, add the applinks:<APPSITE HOST> entry to the Associated Domains Capability of your app.
Also, add the applinks: `<APPSITE HOST>` entry to the Associated Domains Capability of your app.


### Imports
Expand All @@ -43,7 +45,7 @@ From that variable, you have access to all the util methods the plugin implement
### Keycloak.login

```js
Keycloak.login(conf, callback, scope)
Keycloak.keycloakUILogin(conf, callback, scope)
.then((response) => /* Your resolve */ )
.catch((error) => /* Your reject*/ )
```
Expand Down Expand Up @@ -88,7 +90,7 @@ response.tokens = {
### Keycloak.apiLogin

```js
Keycloak.apiLogin(conf, username, password, [scope = 'info'])
Keycloak.login(conf, username, password, [scope = 'info'])
.then((response) => /* Your resolve */ )
.catch((error) => /* Your reject*/ )
```
Expand All @@ -98,14 +100,26 @@ Method arguments:
- _username_: The username to be logged in
- _password_: The password associated to the above username
- _scope_: same behavior as above

```js
Keycloak.refreshLogin([scope = 'info'])
.then((response) => /* Your resolve */ )
.catch((error) => /* Your reject*/ )
```

Method arguments:
- _scope_: same behavior as above

Sometimes you may need to re-login your user w/ Keycloak via the login process but, for some reason, you don't want / can't display the login page.<br>
This method will re-login your user by recycling the last combination of username/password he entered, reading them from the AsyncStorage.

#### Manually handling the tokens

```js
import Keycloak, { TokenStorage } from 'react-native-keycloak-plugin'
```

Logging in by the login function will save the tokens information and the configuration object into the AsyncStorage.<br>Through the TokenStorage object, the plugin exports some methods that can be used to interact with these objects.
Logging in by the login function will save the tokens information, and the configuration object into the AsyncStorage.<br>Through the TokenStorage object, the plugin exports some methods that can be used to interact with these objects.

### Keycloak.retrieveUserInfo
```js
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-keycloak-plugin",
"version": "0.5.1",
"version": "1.0.0",
"description": "Functional React Native module for authentication between a client and the keycloak server.",
"main": "src/index.js",
"scripts": {
Expand Down
7 changes: 4 additions & 3 deletions src/Constants.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const CONFIG = '@keyCloakConfig';
const CREDENTIALS = '@credentials';
const GET = 'GET';
const POST = 'POST';
const URL = 'url';
const TOKENS = '@tokens';
const CONFIG = '@keyCloakConfig';
const URL = 'url';

export { CONFIG, GET, POST, TOKENS, URL };
export { CONFIG, CREDENTIALS, GET, POST, TOKENS, URL };
74 changes: 50 additions & 24 deletions src/Core.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,10 @@ const retrieveTokens = async (conf, code, resolve, reject, deepLinkUrl) => {
}
};


// ### PUBLIC METHODS

export const login = (conf, callback, scope = 'info') => new Promise(((resolve, reject) => {
const { url, state } = getLoginURL(conf, scope);

const listener = event => onOpenURL(conf, resolve, reject, state, event, retrieveTokens);
Linking.addEventListener(URL, listener);

const doLogin = callback || Linking.openURL;
doLogin(url);
}));

export const apiLogin = async (conf, username, password, scope = 'info') => {
const performLogin = async (conf, username, password, scope = 'info') => {
const {
resource, realm, credentials, 'auth-server-url': authServerUrl,
} = conf;

const url = `${getRealmURL(realm, authServerUrl)}/protocol/openid-connect/token`;
const method = POST;
const body = qs.stringify({
Expand All @@ -95,18 +81,56 @@ export const apiLogin = async (conf, username, password, scope = 'info') => {
if (fullResponse.status === 200) {
await TokenStorage.saveConfiguration(conf);
await TokenStorage.saveTokens(jsonResponse);
await TokenStorage.saveCredentials({ username, password });
return jsonResponse;
}

console.error(`Error during kc-api-login, ${fullResponse.status}: ${jsonResponse.url}`);
return Promise.reject(jsonResponse.error_description);
return Promise.reject();
};


// ### PUBLIC METHODS

export const keycloakUILogin = (conf, callback, scope = 'info') => new Promise(((resolve, reject) => {
const { url, state } = getLoginURL(conf, scope);

const listener = event => onOpenURL(conf, resolve, reject, state, event, retrieveTokens);
Linking.addEventListener(URL, listener);

const doLogin = callback || Linking.openURL;
doLogin(url);
}));

export const login = async (conf, username, password, scope = 'info') => performLogin(conf, username, password, scope);

export const refreshLogin = async (scope = 'info') => {
const conf = await TokenStorage.getConfiguration();
if (!conf) {
console.error('Error during kc-refresh-login: Could not read configuration from storage');
return Promise.reject();
}

const credentials = await TokenStorage.getCredentials();
if (!credentials) {
console.error('Error during kc-refresh-login: Could not read from AsyncStorage');
return Promise.reject();
}
const { username, password } = credentials;
if (!username || !password) {
console.error('Error during kc-refresh-login: Username or Password not found');
return Promise.reject();
}

return performLogin(conf, scope, username, password);
};

export const retrieveUserInfo = async () => {
const conf = await TokenStorage.getConfiguration();

if (!conf) {
return Promise.reject(Error('Could not read configuration from storage'));
console.error('Error during kc-retrieve-user-info: Could not read configuration from storage');
return Promise.reject();
}

const { realm, 'auth-server-url': authServerUrl } = conf;
Expand All @@ -129,14 +153,15 @@ export const retrieveUserInfo = async () => {
}

console.error(`Error during kc-retrieve-user-info: ${fullResponse.status}: ${fullResponse.url}`);
return Promise.reject(jsonResponse.error_description);
return Promise.reject();
};

export const refreshToken = async () => {
const conf = await TokenStorage.getConfiguration();

if (!conf) {
return Promise.reject(Error('Could not read configuration from storage'));
console.error('Could not read configuration from storage');
return Promise.reject();
}

const {
Expand Down Expand Up @@ -168,21 +193,23 @@ export const refreshToken = async () => {
}

console.error(`Error during kc-refresh-token, ${fullResponse.status}: ${fullResponse.url}`);
return Promise.reject(jsonResponse.error_description);
return Promise.reject(jsonResponse);
};

export const logout = async () => {
const conf = await TokenStorage.getConfiguration();

if (!conf) {
return Promise.reject(Error('Could not read configuration from storage'));
console.error('Could not read configuration from storage');
return Promise.reject();
}

const { realm, 'auth-server-url': authServerUrl } = conf;
const savedTokens = await TokenStorage.getTokens();

if (!savedTokens) {
return Promise.reject(new Error(`Error during kc-logout, savedTokens is ${savedTokens}`));
console.error(`Error during kc-logout, savedTokens is ${savedTokens}`);
return Promise.reject();
}

const logoutUrl = `${getRealmURL(realm, authServerUrl)}/protocol/openid-connect/logout`;
Expand All @@ -195,7 +222,6 @@ export const logout = async () => {
return Promise.resolve();
}

const jsonResponse = await fullResponse.json();
console.error(`Error during kc-logout: ${fullResponse.status}: ${fullResponse.url}`);
return Promise.reject(jsonResponse.error_description);
return Promise.reject();
};
11 changes: 10 additions & 1 deletion src/TokenStorage.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import AsyncStorage from '@react-native-community/async-storage';
import { TOKENS as TOKENS_KEY, CONFIG as CONFIG_KEY } from './Constants';
import { TOKENS as TOKENS_KEY, CONFIG as CONFIG_KEY, CREDENTIALS } from './Constants';


const TokenStorage = {
saveCredentials: async (credentials) => {
await AsyncStorage.setItem(CREDENTIALS, JSON.stringify(credentials));
},

saveConfiguration: async (conf) => {
await AsyncStorage.setItem(CONFIG_KEY, JSON.stringify(conf));
},
Expand All @@ -11,6 +15,11 @@ const TokenStorage = {
await AsyncStorage.setItem(TOKENS_KEY, JSON.stringify(tokens));
},

getCredentials: async () => {
const credentials = await AsyncStorage.getItem(CREDENTIALS);
return (credentials) ? JSON.parse(credentials) : undefined;
},

getConfiguration: async () => {
const conf = await AsyncStorage.getItem(CONFIG_KEY);
return (conf) ? JSON.parse(conf) : undefined;
Expand Down
5 changes: 3 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { apiLogin, login, logout, refreshToken, retrieveUserInfo } from './Core';
import { keycloakUILogin, login, logout, refreshLogin, refreshToken, retrieveUserInfo } from './Core';

export { default as TokenStorage } from './TokenStorage';
export { TokensUtils } from './Utils';

export default {
apiLogin,
keycloakUILogin,
login,
logout,
refreshLogin,
refreshToken,
retrieveUserInfo,
};

0 comments on commit 882dc84

Please sign in to comment.