Could i login solid pod automatically without opening the solid login page?

i want to save my pod url, username,password in a file, and when i run my program,get username and password from the file,and complete the authentication automatically. Then i can get the data from my pod.
i read the tutotial from Functions — Inrupt solid-client-authn-browser API Documentation, the function handleincomingredirect just can be worked after login.The fuction login (…/solid-client-authn-browser/functions.html#login)open the solid login page,and i need input my information manually.Could i transfer parameters to api to complete login without open the solid login page?

Hello @wenjingli-qa !

Your question is very interesting, and it actually reaches beyond Solid, into the authentication protocol that Solid is based on, namely OpenID Connect (often abbreviated OIDC). You might be interested to look into OpenID Connect FAQ and Q&As | OpenID and see if that helps. If you are really very interested in the area, I started getting a good understanding of the protocol after watching 6/24 OAuth2 Master Class | Identiverse 2018 - YouTube, but that’s a 2h master class so I would absolutely understand that it’s a bit too intense ^^.

Long story short, in order to run a program (be it an in-browser app, a script, a desktop app, a web server…) that accesses “your” data on Solid (i.e. data you have access to on some Solid Pod), in essence you have to delegate your trust to this program. In OIDC, this delegation of trust must happen through a browser interaction, and involves a redirection from the application to the Solid Identity Provider through a Web browser.

I think this means that even if there may exist ways to do what you want to do (which I don’t think there are), you’d be working in kind of a grey area of the protocol, and you’d likely not get great support from existing tooling.

Note that if you’re using @inrupt/solid-client-authn-browser, you may want to look into Session Restore upon Browser Refresh — Inrupt JavaScript Client Libraries if you enable session restore, when they first connect your users would have to go through the redirection I was just describing, but then the Solid Identity Provider sets a cookie on the user’s browser, which allows things to happen without user interaction the next time they open your app. The redirection still happens though (even if only for an instant), so you should be aware that the screen still blinks.

Does that answer your question ? I replied assuming that you were developping an in-browser app, but the situation is a bit different if you are working with NodeJS, so don’t hesitate to give a bit more information on your setup :slight_smile:

1 Like

thanks for your reply :smiley:I’m not developping an in-browser app.In fact,i’m working with NodeJS.I had created an express application.I find a library provides access to Solid: GitHub - solid/solid-node-client: a nodejs client for Solid ,but when i try to authentication for NSS-Style Pods, there a new error i didn’t find the way to solve it :cry: :cry:


app.js

var SolidNodeClient = require('solid-node-client');
const client = new SolidNodeClient.SolidNodeClient();
async function getlogin(){
  try {
    let session = await client.login({
    idp : "https://solidcommunity.net", // e.g. https://solidcommunity.net
    username : "myname",
    password : "mypassword",
  });
  if( session.isLoggedIn ) {let webid = await client.fetch(session.webId); console.log(webid); }
} catch (e) {
    console.log(e, "ERROR");
}

TokenRequester.js

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const tsyringe_1 = require("tsyringe");
const url_parse_1 = __importDefault(require("url-parse"));
const form_urlencoded_1 = __importDefault(require("form-urlencoded"));
let TokenRequester = class TokenRequester {
    constructor(storageUtility, issuerConfigFetcher, fetcher, dpopHeaderCreator, joseUtility) {
        this.storageUtility = storageUtility;
        this.issuerConfigFetcher = issuerConfigFetcher;
        this.fetcher = fetcher;
        this.dpopHeaderCreator = dpopHeaderCreator;
        this.joseUtility = joseUtility;
    }
    async request(localUserId, body) {
        const [issuer, clientId, clientSecret] = await Promise.all([
            this.storageUtility.getForUser(localUserId, "issuer", true),
            this.storageUtility.getForUser(localUserId, "clientId", true),
            this.storageUtility.getForUser(localUserId, "clientSecret")
        ]);
        const issuerConfig = await this.issuerConfigFetcher.fetchConfig(new url_parse_1.default(issuer));
        if (body.grant_type &&
            (!issuerConfig.grantTypesSupported ||
                !issuerConfig.grantTypesSupported.includes(body.grant_type))) {
            throw new Error(`The issuer ${issuer} does not support the ${body.grant_type} grant`);
        }
        if (!issuerConfig.tokenEndpoint) {
            throw new Error(`This issuer ${issuer} does not have a token endpoint`);
        }
        const tokenRequestInit = {
            method: "POST",
            headers: {
                DPoP: await this.dpopHeaderCreator.createHeaderToken(issuerConfig.tokenEndpoint, "POST"),
                "content-type": "application/x-www-form-urlencoded"
            },
            body: form_urlencoded_1.default({
                ...body,
                client_id: clientId
            })
        };
        if (clientSecret) {
            tokenRequestInit.headers.Authorization = `Basic ${this.btoa(`${clientId}:${clientSecret}`)}`;
        }
        const tokenResponse = await (await this.fetcher.fetch(issuerConfig.tokenEndpoint, tokenRequestInit)).json();
        if (!(tokenResponse &&
            tokenResponse.access_token &&
            tokenResponse.id_token &&
            typeof tokenResponse.access_token === "string" &&
            typeof tokenResponse.id_token === "string" &&
            (!tokenResponse.refresh_token ||
                typeof tokenResponse.refresh_token === "string"))) {
            throw new Error("IDP token route returned an invalid response.");
        }
        await this.storageUtility.setForUser(localUserId, "accessToken", tokenResponse.access_token);
        await this.storageUtility.setForUser(localUserId, "idToken", tokenResponse.id_token);
        await this.storageUtility.setForUser(localUserId, "refreshToken", tokenResponse.refresh_token);
        const decoded = await this.joseUtility.decodeJWT(tokenResponse.access_token);
        if (!decoded || !decoded.sub) {
            throw new Error("The idp returned a bad token without a sub.");
        }
        await this.storageUtility.setForUser(localUserId, "webId", decoded.sub);
    }
    btoa(str) {
        return Buffer.from(str.toString(), "binary").toString("base64");
    }
};
TokenRequester = __decorate([
    tsyringe_1.injectable(),
    __param(0, tsyringe_1.inject("storageUtility")),
    __param(1, tsyringe_1.inject("issuerConfigFetcher")),
    __param(2, tsyringe_1.inject("fetcher")),
    __param(3, tsyringe_1.inject("dpopHeaderCreator")),
    __param(4, tsyringe_1.inject("joseUtility")),
    __metadata("design:paramtypes", [Object, Object, Object, Object, Object])
], TokenRequester);
exports.default = TokenRequester;
//# sourceMappingURL=TokenRequester.js.map

@wenjingli-qa I can reproduce your error. That looks like a problem in one of the downstream libraries. I will troubleshoot and post here when I figure it out. Thanks for reporting!

1 Like

@wenjingli-qa I have found the bug and a fix for it. I would much appreciate if you could test the fix and let me know the results.

Here’s what my detective work turned up: Solid-node-client uses solid-auth-fetcher which uses form-urlencoded which has recently slightly changed its interface without a major version change. The fix is to change solid-auth-fetcher to handle both the older and newer form-urlencoded. I’ll submit a patch so there should be a new npm version of solid-node-clien when the patch is accepted by solid-auth-fetcher.

In the mean time, if you follow these steps, you should be able to login to NSS with solid-node-client again.

In the solid-node-client folder, run npm install if you haven’t already then edit the file in ./node_modules/solid-auth-fetcher/dist/login/oidc/TokenRequester.js. remove line 20 which should look like this

const form_urlencoded_1 = __importDefault(require("form-urlencoded")); 

In its place put these lines :

let form_urlencoded_1 = __importDefault(require("form-urlencoded"));
if( typeof form_urlencoded_1.default != "function" ) {
  form_urlencoded_1 = form_urlencoded_1.default ;
} 

If you (or anyone else) has time to try this, please let me know the results.

3 Likes

It’s worked after updating the code in TokenRequester.js as you said. :smiley:
I appreciate your help very much~

2 Likes