forked from mit-oidc-client/mit-oidc-client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathauth.tsx
152 lines (129 loc) · 6.29 KB
/
auth.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import React, { Component } from "react";
import { AUTH_CONFIG } from "./authConfig";
import { generateRandomBytes, toHexString } from "./authHelper";
import Cookies from "universal-cookie";
import { useSearchParams, useNavigate, useLocation } from "react-router-dom";
import { useState, useEffect } from "react";
import { useAuth } from "./authProvider";
import { opkService } from "./pktoken";
import "./auth.css";
/**
* Expected response for server to return to user's browser after querying /login endpoint
*/
interface loginResponse {
success: boolean, //Whether or not we were able to get user's info
error_msg: string, //If failed, provide error message. Else, empty string.
//All the values below will be populated if success,
//otherwise they will be empty strings.
//These are in accordance with: https://github.com/sipb/petrock#what-information-can-i-query
sub: string,
email: string,
affiliation: string,
name: string,
given_name: string,
family_name: string,
//For session management
session_id: string
//For identity management (useful for OpenPubKey extension)
id_token: string
}
/**
* Redirect user to OIDC Authentication Endpoint for login with necessary query parameters
*/
async function redirectToLogin() {
//Generate new state and nonce values
const state = toHexString(generateRandomBytes(AUTH_CONFIG.state_length)); //TODO: Cryptography bind value with a browser cookie
// const nonce = generateRandomBytes(AUTH_CONFIG.nonce_length); //TODO: Save as a HTTP only session cookie
// const nonce_hash = await window.crypto.subtle.digest("SHA-256", nonce).then((hashBuffer) => {
// const hashArray = new Uint8Array(hashBuffer);
// return toHexString(hashArray);
// });
const nonce_hash = await opkService.generateNonce();
const params = new URLSearchParams();
params.append("client_id", AUTH_CONFIG.client_id);
params.append("response_type", AUTH_CONFIG.response_type);
params.append("scope", AUTH_CONFIG.scope);
params.append("redirect_uri", AUTH_CONFIG.redirect_uri);
params.append("state", state);
params.append("nonce", nonce_hash);
//Store the state in localStorage (to be used for code validation)
localStorage.setItem(AUTH_CONFIG.state_localstorage_name, state); //TODO: Do I need to set other security flags
//Store the nonce as a Secure, SameSite cookie (to be sent to backend for ID token validation)
const cookies = new Cookies();
cookies.set(
AUTH_CONFIG.nonce_cookie_name,
// toHexString(nonce),
nonce_hash,
AUTH_CONFIG.nonce_cookie_options
);
const destinationURL = AUTH_CONFIG.auth_endpoint + "?" + params.toString();
window.location.replace(destinationURL);
}
function OidcResponseHandler() {
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const location = useLocation();
const auth = useAuth();
const state = searchParams.get("state");
const code = searchParams.get("code");
let initialMsg: string;
//Validate the state parameter we get back is what we generated on client side
if (state === localStorage.getItem(AUTH_CONFIG.state_localstorage_name)) {
initialMsg = "Waiting to hear back from server..."; //User logged in to OIDC page, but still needs to be logged
//into our backend system.
} else {
initialMsg = "Login Failed. Please try again.";
}
localStorage.removeItem(AUTH_CONFIG.state_localstorage_name); //Remove state variable after validation
const [loginMsg, setLoginMsg] = useState(initialMsg);
/**
* Should be called only once (e.g. upon successful login to OIDC endpoint).
*/
useEffect(() => {
//Note: We're using an async wrapper to make easier to work with
//results from fetch() since useEffect is a synchronous function
/**
* Validates and sends the received user `code` to the backend
* for token retrieval and validation, then parses the result
* browser-side
*/
async function sendCode(): Promise<void> {
const requestOptions: RequestInit = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code: code }),
credentials: "include" //Should include nonce, which is an HTTPonly cookie
};
//Send user's code to backend server
const response = await fetch(AUTH_CONFIG.login_uri, requestOptions);
const data: loginResponse = await response.json();
if (data.success) {
//Login was successful! Expect id_token
setLoginMsg("Login successful!");
localStorage.setItem(AUTH_CONFIG.idtoken_localstorage_name, data.id_token); //Save id_token to local storage
localStorage.setItem(AUTH_CONFIG.useremail_localstoragge_name, data.email);
localStorage.setItem(AUTH_CONFIG.sessionid_localstorage_name, data.session_id); //Save session_id to local storage
//NOTE: If you want to do more with the additional user profile
// information (such as full name, family name, given name,
// MIT affiliation, you can do so here).
// Otherwise, they are not saved to localstorage by default
// out of privacy concerns.
const pktoken = await opkService.generatePKToken(data.id_token);
console.log("PKTOKEN GENERATED", pktoken);
const ver = await opkService.verifyPKToken(pktoken);
console.log("PKTOKEN VERIFIED", ver);
const from = location.state?.from?.pathname || "/";
auth.signin(data.email, () => {
//Redirect user back to their original location
navigate(from, { replace: true });
});
} else {
//Login was unsuccessful. Let user know about error message.
setLoginMsg(`Login failed! ${data.error_msg}`);
}
}
sendCode();
}, [navigate, code, auth, location]);
return <div className="OidcResponseHandler"><h1>{loginMsg}</h1></div>;
}
export { redirectToLogin, OidcResponseHandler };