OneDrive / samples

Contains samples, scenarios, and guidance for integrating with OneDrive and SharePoint drives, drive items, and files.
MIT License
57 stars 57 forks source link

Getting the picker v8.0 to access with MS Graph Token #3

Open gaetanDev7 opened 2 years ago

gaetanDev7 commented 2 years ago

Hello,

I've implemented a picker to access OneDrive/ Sharepoint using V 8.0 SDK. Our platform is heavily dependent on Microsoft Graph to perform access for all our customers.

Here is the implementation example:

import { useEffect, useState } from 'react';
import { ODPickerCallBack, ODPickerConfiguration } from './Types';

let win: any = null;
let port: any = null;
const baseUrl = 'https://xxxxxxxxx.sharepoint.com';

export const useOneDrivePicker = (getToken: () => Promise<any>) => {
    const [selectedOneDriveFileCallBackInfo, setSelectedOneDriveFileCallBackInfo] = useState<ODPickerCallBack>();
    const [selectedCancelOneDriveCallBackInfo, setSelectedCancelOneDriveCallBackInfo] = useState<boolean>();
    const [errorOneDriveCallBackInfo, setErrorOneDriveCallBackInfo] = useState<unknown>();

    const OnSuccessCallback = (files: ODPickerCallBack) => {
        setSelectedOneDriveFileCallBackInfo(files);
    };

    const OnCancelCallback = () => {
        setSelectedCancelOneDriveCallBackInfo(true);
    };

    const OnErrorCallback = (error: unknown) => {
        setErrorOneDriveCallBackInfo(error);
    };

    /**
     * Combines an arbitrary set of paths ensuring and normalizes the slashes
     *
     * @param paths 0 to n path parts to combine
     */
    function combine(...paths: any[]) {
        return paths
            .map((path) => path.replace(/^[\\|/]/, '').replace(/[\\|/]$/, ''))
            .join('/')
            .replace(/\\/g, '/');
    }

    // the options we pass to the picker page through the querystring
    const params = {
        sdk: '8.0',
        entry: {
            oneDrive: {
                files: {},
            },
        },
        selection: {
            mode: 'single',
        },
        authentication: {},
        messaging: {
            origin: 'http://localhost:3000',
            channelId: '400',
        },
        typesAndSources: {
            mode: 'files',
            pivots: {
                oneDrive: true,
                recent: true,
                sharedLibraries: true,
            },
        },
    };

    async function messageListener(message: any) {
        // get a graph token
        const token = await getToken();
        console.log('token in message=', token);
        switch (message.data.type) {
            case 'notification':
                console.log(`notification: ${message.data}`);
                break;

            case 'command':
                port.postMessage({
                    type: 'acknowledge',
                    id: message.data.id,
                });

                const command = message.data.data;
                // eslint-disable-next-line no-case-declarations
                switch (command.command) {
                    case 'authenticate':
                        if (typeof token !== 'undefined' && token !== null) {
                            port.postMessage({
                                type: 'result',
                                id: message.data.id,
                                data: {
                                    result: 'token',
                                    token,
                                },
                            });
                        } else {
                            console.error(`Could not get auth token for command: ${JSON.stringify(command)}`);
                        }

                        break;

                    case 'close':
                        win.close();
                        break;

                    case 'pick':
                        console.log(`Picked: ${JSON.stringify(command)}`);

                        console.log(`<pre>${JSON.stringify(command, null, 2)}</pre>`);

                        port.postMessage({
                            type: 'result',
                            id: message.data.id,
                            data: {
                                result: 'success',
                            },
                        });

                        win.close();

                        break;

                    default:
                        console.warn(`Unsupported command: ${JSON.stringify(command)}`, 2);

                        port.postMessage({
                            result: 'error',
                            error: {
                                code: 'unsupportedCommand',
                                message: command.command,
                            },
                            isExpected: true,
                        });
                        break;
                }

                break;
            default:
                console.warn(`Unsupported message: ${JSON.stringify(message.data.type)}`, 2);
        }
    }

    const openOneDrivePicker = (odPickerConfig: ODPickerConfiguration) => {
        // Everything went fine, create the picker.
        if (odPickerConfig.accessToken) {
            console.log('token=', odPickerConfig.accessToken);

            win = window.open('', 'Picker', 'width=800,height=600');

            if (!win) return;

            const queryString = new URLSearchParams({
                filePicker: JSON.stringify(params),
            });

            const url = combine(baseUrl, `_layouts/15/FilePicker.aspx?${queryString}`);

            const form = win.document.createElement('form');
            form.setAttribute('action', url);
            form.setAttribute('method', 'POST');
            win.document.body.append(form);

            const input = win.document.createElement('input');
            input.setAttribute('type', 'hidden');
            input.setAttribute('name', 'access_token');
            input.setAttribute('value', odPickerConfig.accessToken);
            form.appendChild(input);

            form.submit();

            window.addEventListener('message', (event) => {
                if (event.source && event.source === win) {
                    const message = event.data;

                    if (message.type === 'initialize' && message.channelId === params.messaging.channelId) {
                        port = event.ports[0];

                        port.addEventListener('message', messageListener);

                        port.start();

                        port.postMessage({
                            type: 'activate',
                        });
                    }
                }
            });
        } else {
            console.error('oauth Token no found:');
        }

        // eslint-disable-next-line consistent-return
        return true;
    };

    return {
        openOneDrivePicker,
        selectedOneDriveFileCallBackInfo,
        selectedCancelOneDriveCallBackInfo,
        errorOneDriveCallBackInfo,
    };
};

You will find below a screenshot of the error message :

test drawio

So basically, in my MS Graph token my ‘audience’ is as follows: "aud": "https://graph.microsoft.com/"

Is there currently a way to make the picker work with MS Graph ? Please advise.

Thanks in advance for your Help.

patrick-rodgers commented 2 years ago

No, the picker does not currently call the graph APIs. This is great feedback we will share with the feature team, but there is currently not a timeline to switch the backend to Microsoft Graph.

gaetanDev7 commented 2 years ago

Thank you @patrick-rodgers for this information. Hope to be able to have the picker soon connected to Microsoft Graph.

Tjerkdb commented 2 years ago

Very much hoping this can be implemented!

tstrader commented 1 year ago

Same here... really difficult to work with this picker when it effectively means we need 3 different auth tokens for the solution to work....

scottsidwell commented 1 year ago

I've recently been investigating integrating Microsoft file-picking into our app, and ultimately concluded that v7.2 is a clearer path forward for us.

As a consumer, I don't want to think or understand the nuances of Microsoft's multiple file-management services - the moment I have to start worrying about the differences between one-drive or sharepoint configurations, the surface area of what I need to test as an app developer has doubled and my confidence that future changes won't subtly break behaviour is halved.

Which is a bit of shame v8 doesn't do this for us, hopefully MS Graph support is roadmapped soon 🤞

eclrbohnhoff commented 1 year ago

I was forced to use v7.2 API since it is required that we must use MS Graph. Adding +1 here that it is added to the roadmap for v8.

armasson commented 1 year ago

Hey team,

It looks like we have the same problem.

We were using the File picker SDK and we tried to use the access token we already have for the application.

Technically speaking the load is done as it:

// Init the library with an empty auth
new msal.PublicClientApplication({ auth: { clientId: '...'}})
...
// Then generate the form with the local token
 win = window.open('', 'Picker', 'width=800,height=600') as WindowProxy;
const authToken = await getToken();
const queryString = new URLSearchParams({
    filePicker: JSON.stringify({...}),
});

const url = combine(baseUrl, `_layouts/15/FilePicker.aspx?${queryString}`);

const form = win.document.createElement('form');
form.setAttribute('action', url);
form.setAttribute('method', 'POST');
win.document.body.append(form);

const input = win.document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', 'access_token');
input.setAttribute('value', authToken);
form.appendChild(input);

form.submit();

using it, we have this error:

     "status": 401,
     "authenticate":  "Bearer realm=\"d49cbc78-6d92-451d-84a3-b04cd87d201f\",client_id=\"00000003-0000-0ff1-ce00-000000000000\",trusted_issuers=\"00000001-0000-0000-c000-000000000000@*,D3776938-3DBA-481F-A652-4BEDFCAB7CD8@*,https://sts.windows.net/*/,https://login.microsoftonline.com/*/v2.0,00000003-0000-0ff1-ce00-000000000000@90140122-8516-11e1-8eff-49304924019b\",authorization_uri=\"https://login.microsoftonline.com/common/oauth2/authorize\"",
     "code": "3000003,invalid_client",
                "message": "Exception of type 'Microsoft.IdentityModel.Tokens.AudienceUriValidationFailedException' was thrown.",
                "codes": {
                    "order": [
                        "x-ms-diagnostics"
                    ],
                    "map": {
                        "x-ms-diagnostics": {
                            "source": "x-ms-diagnostics",
                            "code": "3000003,invalid_client",
                            "message": "Invalid audience Uri 'https://graph.microsoft.com/'.",
                            "isDisplayable": true
                        }
                    }
                },
                ...

Here is the token information used:

{
  "aud": "https://graph.microsoft.com",
  "iss": "https://sts.windows.net/d49cbc78-6d92-451d-84a3-b04cd87d201f/"
  ...
  "appid": "4af4d3f2-******",
   "scp": "... Files.ReadWrite.All ... Sites.Read.All ...",
}

At first sight it seems the audience https://graph.microsoft.com/, can not be used. If so, how can we use the librairie to access the graph API ? Has there been any progress on this ? 🙏

Tjerkdb commented 11 months ago

Is there any update on this? Is this being considered at all?

JCrew0 commented 8 months ago

@Tjerkdb @armasson We have it logged on our end that we'd like to support Graph. However, now that we no longer have a javascript wrapper like v7.2 did, there's new security concerns for switching to Graph. We hope to work with the auth team to get this supported, but in the meantime, v7.2 might be best for users reliant on Graph.