Implémentation du modèle d'authentification "Authorization code with PKCE [1]" [1] https://auth0.com/docs/api-auth/tutorials/authorization-code-grant-pkce
98 lines
3.0 KiB
TypeScript
98 lines
3.0 KiB
TypeScript
import { put, takeLatest, all } from 'redux-saga/effects';
|
|
import {
|
|
LOGOUT, LOGIN_REQUEST,
|
|
HANDLE_OAUTH2_CALLBACK_REQUEST, handleOAuth2CallbackAction,
|
|
HANDLE_OAUTH2_CALLBACK_FAILURE, handleOAuth2CallbackSuccess,
|
|
parseIdTokenAction, parseIdToken,
|
|
PARSE_ID_TOKEN_REQUEST, PARSE_ID_TOKEN_FAILURE, parseIdTokenSuccess,
|
|
setCurrentUser, LOGIN_FAILURE,
|
|
} from '../actions/auth';
|
|
import {
|
|
createLoginSession, LoginSession,
|
|
createAccessTokenRequest, saveAccessGrant,
|
|
saveLoginSessionState, getSavedLoginSessionState,
|
|
getLogoutURL, getSavedAccessGrant, clearAccessGrant
|
|
} from '../../util/auth';
|
|
import qs from 'qs';
|
|
import { UnauthorizedError } from '../../util/daddy';
|
|
import jwtDecode from 'jwt-decode';
|
|
import { IdToken } from '../../types/idToken';
|
|
|
|
export function* authRootSaga() {
|
|
yield all([
|
|
takeLatest(LOGIN_REQUEST, loginSaga),
|
|
takeLatest(LOGOUT, logoutSaga),
|
|
takeLatest(HANDLE_OAUTH2_CALLBACK_REQUEST, handleOAuth2CallbackSaga),
|
|
takeLatest(PARSE_ID_TOKEN_REQUEST, parseIDTokenSaga),
|
|
]);
|
|
}
|
|
|
|
export function* loginSaga() {
|
|
try {
|
|
const loginSession: LoginSession = yield createLoginSession();
|
|
console.log('Code verifier is ', loginSession.verifier);
|
|
console.log('State is ', loginSession.state);
|
|
saveLoginSessionState(loginSession.verifier, loginSession.state);
|
|
window.location.replace(loginSession.redirectUrl);
|
|
} catch(err) {
|
|
yield put({ type: LOGIN_FAILURE, err });
|
|
}
|
|
}
|
|
|
|
export function* logoutSaga() {
|
|
const accessGrant = getSavedAccessGrant();
|
|
const logoutURL = getLogoutURL(accessGrant.id_token);
|
|
clearAccessGrant();
|
|
window.location.replace(logoutURL);
|
|
}
|
|
|
|
export function* handleOAuth2CallbackSaga({ search }: handleOAuth2CallbackAction) {
|
|
const query = search.substring(1);
|
|
const params = qs.parse(query);
|
|
|
|
const loginSession = getSavedLoginSessionState();
|
|
|
|
console.log('Stored state verifier is', loginSession.state);
|
|
if (loginSession.state !== params.state) {
|
|
yield put({ type: HANDLE_OAUTH2_CALLBACK_FAILURE, err: new Error("Invalid state") });
|
|
return;
|
|
}
|
|
|
|
console.log('Stored code verifier is', loginSession.verifier);
|
|
console.log('Authorization code is', params.code);
|
|
|
|
const req = createAccessTokenRequest(params.code as string, loginSession.verifier);
|
|
|
|
let grant;
|
|
try {
|
|
grant = yield fetch(req.url, { method: "POST", body: req.data })
|
|
.then(res => {
|
|
if (res.status === 401) return Promise.reject(new UnauthorizedError());
|
|
return res;
|
|
})
|
|
.then(res => res.json());
|
|
} catch(err) {
|
|
yield put({ type: HANDLE_OAUTH2_CALLBACK_FAILURE, err });
|
|
return;
|
|
}
|
|
|
|
console.log("Access grant is", grant);
|
|
saveAccessGrant(grant);
|
|
|
|
yield put(handleOAuth2CallbackSuccess(grant));
|
|
yield put(parseIdToken(grant.id_token));
|
|
};
|
|
|
|
|
|
export function* parseIDTokenSaga({ rawIdToken }: parseIdTokenAction) {
|
|
let idToken: IdToken;
|
|
try {
|
|
idToken = jwtDecode(rawIdToken);
|
|
} catch(err) {
|
|
yield put({ type: PARSE_ID_TOKEN_FAILURE, err });
|
|
return;
|
|
}
|
|
|
|
yield put(parseIdTokenSuccess(idToken));
|
|
yield put(setCurrentUser(idToken.email));
|
|
}; |