I have tested my idea and it is working. Basically I retrieve the jsonwebtoken with a call of currentSession() to the solid-auth-client on the client side. I can use this token then to authorize users within my back-end api. I just put the token as usual in the request header.
On my back-end i can determine the issuer of the token by decoding it. After that I request the public keys from the issuer and verify the token with that keys. Because the token is containing the users webid i can use his webid for access controll to my api endpoints. For the management of the json web keys and the verification of the token I have used thepanva/jose library for node.
The following code is just a schematic example written in TypeScript. The Clientcode is running in an angular web app and the server code is running on a express/node.js server. I have written a brief typedefinion file for the solid-auth-client. Maybe I will publish it in future. But at the moment its is not exhaustive and not tested.
Client Code:
//Import typedefinitions for the solid-auth-client
import SolidAuthClient from ../../assets/types/solid-auth-client";
//This is a little bit confusing, but it works without an import, because //webpack exposes the authclient at solid.auth
declare namespace solid {
let auth: SolidAuthClient;
}
solid.auth.currentSession()
.then((session) => {
//Get your jsonwebtoken
let jwt = session.authorization.id_token;
//<NOW YOU CAN SEND THE TOKEN WITH YOUR REQUESTS TO THE BACK-END>
});
The typedefinition file for the auth-client:
import {EventEmitter} from 'events
declare type RequestOptions = object;
declare type window = object;
//*******IPC*********
/**
* Makes remote procedure calls.
*/
export class Client {
_serverWindow: window;
_serverOrigin: string;
constructor(serverWindow: window, serverOrigin: string);
request(method: string, ...args: any[]): Promise<any>;
}
//*******STORAGE***********
export const NAMESPACE = 'solid-auth-client'
export interface SyncStorage {
length: number;
key(index: number): string;
getItem(key: string): any;
setItem(key: string, value: any)
removeItem(key: string);
clear();
}
export interface AsyncStorage {
getItem(key: string): Promise<string|null|undefined>;
setItem(key: string, val: string): Promise<void>;
removeItem(key: string): Promise<void>;
}
export type Storage = SyncStorage|AsyncStorage
export const defaultStorage: () => AsyncStorage;
/**
* Gets the deserialized stored data
*/
export function getData(store: Storage): Promise<object>;
/**
* Updates a Storage object without mutating its intermediate representation.
*/
export function updateStorage(store: Storage, update: (oldData: object) => object): Promise<object>;
/**
* Takes a synchronous storage interface and wraps it with an async interface.
*/
export function asyncStorage(storage: Storage): AsyncStorage;
export const memStorage : (storage: Storage) => AsyncStorage;
export function ipcStorage(client: Client): AsyncStorage;
//****************SESSION**************
export type webIdOidcSession = {
idp: string,
webId: string,
accessToken: string,
idToken: string,
clientId: string,
sessionKey: string
}
export type Session = webIdOidcSession
export function getSession(storage: AsyncStorage): Promise<Session|null|undefined>;
export function saveSession(storage: AsyncStorage): (session: Session) => Promise<Session>;
export function clearSession(storage: AsyncStorage): Promise<void>
//*********AUTH CLIENT****************
// Store the global fetch, so the user is free to override it
export const globalFetch: object;
export type loginOptions = {
callbackUri?: string,
popupUri?: string,
storage?: any //AsyncStorage
}
export default class SolidAuthClient extends EventEmitter {
_pendingSession: Promise<Session|null|undefined>|null|undefined;
fetch(input: RequestInfo, options?: RequestOptions): Promise<Response>;
login(idp: string, options: loginOptions): Promise<Session|null|undefined>;
popupLogin(options: loginOptions): Promise <Session|null|undefined >;
currentSession(storage?: AsyncStorage): Promise <Session|null|undefined>;
trackSession(callback: (session: Session|null|undefined) => any): Promise <void>;
logout(storage?: AsyncStorage): Promise <void>;
}
export function defaultLoginOptions(url: string|null|undefined): loginOptions;
ServerCode
import {JWK, JWT} from "@panva/jose";
import * as https from "https";
const { JWKS: { KeyStore } } = require('@panva/jose')
//.....
//<REQUEST WITH THE TOKEN IN THE HEADER>
//<EXTRACT THE TOKEN>
//idToken = ...
//Decode the token and extract the issuer
let decToken = <any>JWT.decode(idToken);
let iss = decToken['iss'];
//Verify the token by requesting the public keys from the issuer
https.get(iss+/jwks", (res) => {
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => {
rawData += chunk;
});
res.on('end', () => {
try {
//Parse the received keys to a JSON-Object
let jwks = JSON.parse(rawData);
//Create a keystore containing the keys
let keyStore = KeyStore.fromJWKS(jwks);
//Validate the token with the public keys
//The call is successfull if one of the keys match
//<IMPORTANT Add more claims here to be checked to enhance the security!!!>
jose.JWT.verify(idToken, keyStore);
//get the users webid
let webId = decToken['sub'];
} catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});