Skip to content

Commit

Permalink
Device flow
Browse files Browse the repository at this point in the history
  • Loading branch information
oharsta committed Aug 20, 2024
1 parent 82cc504 commit a00a7a8
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 27 deletions.
5 changes: 2 additions & 3 deletions oidc-playground-client/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,8 @@ export function postRefreshToken(body) {
return postPutJson(`/oidc/api/refresh_token`, body, "POST");
}


export function postDeviceAuthorization(body) {
xxxxxx
export function postPollDeviceAuthorization(body) {
return postPutJson(`/oidc/api/poll_device_authorization`, body, "POST");
}

export function decodeJWT(jwt) {
Expand Down
16 changes: 11 additions & 5 deletions oidc-playground-client/src/components/Request.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {InfoLabel} from "./InfoLabel";
import "./Requests.scss";
import {
authorizationRequestT,
clientAssertionToolTip, deviceAuthorizationT,
clientAssertionToolTip,
deviceAuthorizationT,
discoveryT,
introspectT,
signedJWTRequestParameterT,
Expand All @@ -30,6 +31,7 @@ export const Request = observer(() => {
}
const {request_url, request_headers, request_body, result} = store.request || {};
const {processingTime} = store;
const {deviceAuthentication} = store;

const queryParameters = {};
if (authorization_url) {
Expand Down Expand Up @@ -125,13 +127,17 @@ export const Request = observer(() => {
</div>
)}

{(request_url && request_url.endsWith("device_authorization")) &&
{deviceAuthentication &&
<div className="fieldset">
<label>Device Verification Codes</label>
<div className="__json-pretty__ device_authorization">
<a href={result.verification_uri} target="_blank">Verification URI - {result.verification_uri}</a>
<a href={result.verification_uri_complete} target="_blank">Verification URI Complete - {result.verification_uri_complete}</a>
<img src={`data:image/png;base64,${result.qr_code}`} alt="qr-code"/>
<a href={deviceAuthentication.verification_uri}
rel="noreferrer"
target="_blank">Verification URI - {deviceAuthentication.verification_uri}</a>
<a href={deviceAuthentication.verification_uri_complete}
rel="noreferrer"
target="_blank">Verification URI Complete - {deviceAuthentication.verification_uri_complete}</a>
<img src={`data:image/png;base64,${deviceAuthentication.qr_code}`} alt="qr-code"/>


</div>
Expand Down
12 changes: 8 additions & 4 deletions oidc-playground-client/src/components/SettingsForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,15 @@ export const SettingsForm = observer(props => {
onChange={val => onChange("signedJWT", val)}
moderators={{auth_protocol, frontChannelTokenRequest}}/>}

<fieldset>
<button type="submit" className="button blue">
Submit
<fieldset>
<button type="reset" className="button"
onClick={() => document.location.replace("/")}>
Reset
</button>
</fieldset>
<button type="submit" className="button blue">
Submit
</button>
</fieldset>
</form>
);
});
5 changes: 5 additions & 0 deletions oidc-playground-client/src/pages/Config.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ export const Config = observer(
store.clientCredentialsAccessToken = json.result.access_token;
store.activeTab = "JWT";
}

if (json.result && json.result.device_code) {
store.deviceAuthentication = json.result;
store.activeTab = "Request";
}
})
.catch(err =>
err.json().then(
Expand Down
26 changes: 14 additions & 12 deletions oidc-playground-client/src/pages/RetrieveContent.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from "react";
import {observer} from "mobx-react";
import store from "store";
import {discovery, postIntrospect, postRefreshToken, postUserinfo} from "../api";
import {discovery, postIntrospect, postPollDeviceAuthorization, postRefreshToken, postUserinfo} from "../api";

export const RetrieveContent = observer(props => {
const accessToken = store.normalFlowAccessToken || store.hybridFlowAccessToken || store.clientCredentialsAccessToken ||
((store.request || {}).result || {}).access_token;

const refreshToken = store.refreshToken || ((store.request || {}).result || {}).refresh_token;
const deviceCode = (store.deviceAuthentication || {}).device_code;
const body = {
token: accessToken,
refresh_token: refreshToken,
Expand Down Expand Up @@ -45,9 +45,10 @@ export const RetrieveContent = observer(props => {
.then(handleResult)
.catch(err => handleError(err, "refresh_token"));

const handleDeviceResult = () => postDeviceAuthorization({...state.form, ...store.config, ...body})
const handleDeviceResult = () => postPollDeviceAuthorization({...state.form, ...store.config, ...body, ...store.deviceAuthentication})
.then(handleResult)
.catch(err => handleError(err, "device_authorize"));

const handleDiscovery = () => discovery().then(res => {
delete res.remote_client_id;
delete res.redirect_uri;
Expand All @@ -73,23 +74,24 @@ export const RetrieveContent = observer(props => {
disabled={!(accessToken && !store.clientCredentialsAccessToken)}
onClick={handleUserInfo}>Userinfo
</button>
{accessToken && <button
<button
type="button"
className="button introspect"
disabled={!(accessToken)}
onClick={handleIntrospect}>Introspect
</button>}
{refreshToken && <button
</button>
<button
type="button"
className="button refresh-token"
disabled={!refreshToken}
onClick={handleRefreshToken}>Refresh token
</button>}
{(store.request || {}).device_code && <button
type="button"
className="button refresh-token"
onClick={handleDeviceResult}>Poll Device Request
</button>}
</button>
{deviceCode &&
<button type="button"
className="button refresh-token"
onClick={handleDeviceResult}>Poll Device Request
</button>
}
<button
type="button"
className="button discovery"
Expand Down
4 changes: 3 additions & 1 deletion oidc-playground-client/src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Store {
configLoaded = false;
activeTab = "JWT";
apiCall = false;
deviceAuthentication = undefined;
config = {
acr_values_supported: [],
claims_parameter_supported: false,
Expand Down Expand Up @@ -45,7 +46,8 @@ decorate(Store, {
config: observable,
configLoaded: observable,
activeTab: observable,
apiCall: observable
apiCall: observable,
deviceAuthentication: observable
});

const store = new Store();
Expand Down
23 changes: 21 additions & 2 deletions oidc-playground-server/src/main/java/playground/api/Oidc.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.GrantType;
import com.nimbusds.oauth2.sdk.ResponseType;
import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
import com.nimbusds.oauth2.sdk.auth.JWTAuthentication;
Expand Down Expand Up @@ -248,14 +249,14 @@ public Map<String, Object> deviceFlow(@RequestBody Map<String, Object> body) thr
Map<String, String> parameters = new HashMap<>();

parameters.put("client_id", (String) body.getOrDefault("client_id", clientId));
parameters.put("acr_values", (String) body.get("acr_values"));

String loginHint = (String) body.get("login_hint");
if (StringUtils.hasText(loginHint)) {
parameters.put("login_hint", loginHint);
}

addPromptValues(body, parameters);

addScopeValues(body, parameters);

String endpoint = (String) readWellKnownConfiguration().get("device_authorization_endpoint");
Expand Down Expand Up @@ -316,6 +317,24 @@ public Map<String, Object> userinfo(@RequestBody Map<String, Object> body) throw
return callPostEndpoint(requestBody, (String) readWellKnownConfiguration().get("userinfo_endpoint"), builder);
}

@PostMapping("/poll_device_authorization")
public Map<String, Object> pollDeviceAuthorization(@RequestBody Map<String, Object> body) throws URISyntaxException, JOSEException {
String endpoint = (String) readWellKnownConfiguration().get("token_endpoint");
RequestEntity.BodyBuilder builder = RequestEntity
.post(new URI(endpoint))
.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_FORM_URLENCODED);
String clientIdToUse = (String) body.get("client_id");
clientIdToUse = StringUtils.hasText(clientIdToUse) ? clientIdToUse : clientId;

Map<String, String> requestBody = Map.of(
"client_id", clientIdToUse,
"grant_type", GrantType.DEVICE_CODE.getValue(),
"device_code", (String) body.get("device_code")
);
return callPostEndpoint(requestBody, endpoint, builder);
}

@GetMapping("/decode_jwt")
public String decodeJwtToken(@RequestParam("jwt") String jwt) throws ParseException {
if (uuidPattern.matcher(jwt).matches()) {
Expand Down Expand Up @@ -504,7 +523,7 @@ private void sanitizeMap(Map<String, Object> body) {

private Map<String, String> anonymizeInformation(Map<String, String> headers) {
Map<String, String> result = new HashMap<>(headers);
List<String> sensitiveHeaders = Arrays.asList("client_id", "client_secret", AUTHORIZATION);
List<String> sensitiveHeaders = Arrays.asList("client_secret", AUTHORIZATION);
sensitiveHeaders.forEach(header -> result.replace(header, "XXX"));
return result;
}
Expand Down

0 comments on commit a00a7a8

Please sign in to comment.