Api token authorization openid connect

Does anyone know how to access APIs using a token generated by OpenID Connect? I’m trying to pass a token generated by Keycloak, but I’m getting the error “missing, malformed, expired or otherwise invalid token provided” with a 401 status. However, if the same token is generated by the browser’s network inspector (after logging in with Keycloak), it works. Does anyone know how to interact with OpenID Connect and APIs?

It should work if you create an API token in the user settings.

How should I do it? Does it work for a single user or is it general?

Api tokens are scoped to a single user, every action you do with them appear in Vikunja as if that user has done the action.

1 Like

Settings → API-Tokens (for your user).
In #2644 - GET /user does not return the current user when using an api token - vikunja - Gitea there are some example requests.
I get the same error as you for the /user endpoint. But all other endpoints (as far as I tried) are working fine.


So I need to set up this token. Once that’s done, what should I do next? Is there a way to manage this for a general user? I need to use Vikunja to integrate it into a larger app. So, I want to allow users to access Vikunja directly without having to set this token every time.

You could do the openid handshake manually to exchange an openid auth token for a Vikunja token. Take a look at how Vikunja does this. You’ll also need to manually renew the token in that case. If you want to skip the renewal, you could also use the exchanged token to create an api token using that token and the api with the appropriate api endpoint.

Curious, what app are you integrating Vikunja with?

I’d like to integrate it with an LLM. I’m not sure if I’ll be able to, I’m experimenting.

Would love to hear if it’s working, keep us posted!

1 Like

import axios from ‘axios’;

// Configuration settings
const config = {
keycloak: {
tokenUrl: ‘http://keycloak.local:8080/realms/Chronorix/protocol/openid-connect/token’,
clientId: ‘vikunja-client’,
clientSecret: ‘wpIxOSn5X5A7BOzRGfv8ovPzqpI0tup2’,
username: ‘admin’,
password: ‘password’,
grantType: ‘password’
},
vikunja: {
provider: ‘Keycloak’, // ID of the provider configured in Vikunja
callbackUrl: ‘http://vikunja.local:3456/api/v1/auth/openid/keycloak/callback’,
redirectUrl: ‘http://vikunja.local:3456/auth/openid/keycloak’,
scope: ‘openid profile email’
}
};

// Function to get a Keycloak access token
async function getKeycloakToken() {
try {
const response = await axios.post(
config.keycloak.tokenUrl,
new URLSearchParams({
grant_type: config.keycloak.grantType,
client_id: config.keycloak.clientId,
client_secret: config.keycloak.clientSecret,
username: config.keycloak.username,
password: config.keycloak.password
}),
{
headers: {
‘Content-Type’: ‘application/x-www-form-urlencoded’
}
}
);
return response.data.access_token;
} catch (error) {
console.error(‘Error getting Keycloak access token:’, error.response ? error.response.data : error.message);
throw error;
}
}

// Funzione per generare un nuovo token API in Vikunja
async function createApiToken() {
try {
// Ottieni il token JWT da Keycloak
const keycloakToken = await getKeycloakToken();

    // Configura le opzioni per la richiesta
    const response = await axios.post(
        'http://vikunja.local:3456/api/v1/tokens', // Cambia l'URL se necessario
        {
            title: 'Nuovo Token API', // Nome umano per il token
            permissions: {
                tasks: ['read_all', 'update'] // Permessi richiesti
            }
        },
        {
            headers: {
                'Authorization': `Bearer ${keycloakToken}`,
                'Content-Type': 'application/json'
            }
        }
    );

    // Il token generato sarà visibile nella risposta
    console.log('Nuovo token API creato:', response.data.token);
    return response.data.token;
} catch (error) {
    console.error('Errore nella creazione del token API:', error.response ? error.response.data : error.message);
    throw error;
}

}

(async () => {
try {
const token = await createApiToken();
console.log(‘Token API:’, token);
} catch (error) {
console.error(‘Errore:’, error);
}
})();

Keep responding with missing, malformed, expired, or otherwise invalid token provided.

Which part of it? Also can you enclose the code in backticks(```)? It’s really hard to read currently.

import axios from ‘axios’;

// Configuration settings
const config = {
keycloak: {
tokenUrl: ‘http://keycloak.local:8080/realms/Chronorix/protocol/openid-connect/token’,
clientId: ‘vikunja-client’,
clientSecret: ‘wpIxOSn5X5A7BOzRGfv8ovPzqpI0tup2’,
username: ‘admin’,
password: ‘password’,
grantType: ‘password’
},
vikunja: {
provider: ‘Keycloak’, // ID of the provider configured in Vikunja
callbackUrl: ‘http://vikunja.local:3456/api/v1/auth/openid/keycloak/callback’,
redirectUrl: ‘http://vikunja.local:3456/auth/openid/keycloak’,
scope: ‘openid profile email’
}
};

// Function to get a Keycloak access token
async function getKeycloakToken() {
try {
const response = await axios.post(
config.keycloak.tokenUrl,
new URLSearchParams({
grant_type: config.keycloak.grantType,
client_id: config.keycloak.clientId,
client_secret: config.keycloak.clientSecret,
username: config.keycloak.username,
password: config.keycloak.password
}),
{
headers: {
‘Content-Type’: ‘application/x-www-form-urlencoded’
}
}
);
return response.data.access_token;
} catch (error) {
console.error(‘Error getting Keycloak access token:’, error.response ? error.response.data : error.message);
throw error;
}
}

// Funzione per generare un nuovo token API in Vikunja
async function createApiToken() {
try {
// Ottieni il token JWT da Keycloak
const keycloakToken = await getKeycloakToken();

    // Configura le opzioni per la richiesta
    const response = await axios.post(
        'http://vikunja.local:3456/api/v1/tokens', // Cambia l'URL se necessario
        {
            title: 'Nuovo Token API', // Nome umano per il token
            permissions: {
                tasks: ['read_all', 'update'] // Permessi richiesti
            }
        },
        {
            headers: {
                'Authorization': `Bearer ${keycloakToken}`,
                'Content-Type': 'application/json'
            }
        }
    );

    // Il token generato sarà visibile nella risposta
    console.log('Nuovo token API creato:', response.data.token);
    return response.data.token;
} catch (error) {
    console.error('Errore nella creazione del token API:', error.response ? error.response.data : error.message);
    throw error;
}
}

(async () => {
try {
const token = await createApiToken();
console.log(‘Token API:’, token);
} catch (error) {
console.error(‘Errore:’, error);
}
})();

The function to retrieve the Keycloak token works fine, while createApiToken() does not. It returns:

{
  message: 'missing, malformed, expired or otherwise invalid token provided'
}

Sorry for not putting everything in quotes; I didn’t know how to do it. If you prefer, I can translate the comments from Italian to English if it’s more convenient for you. I really need help.

Looks like you’re doing a post request but the endpoint to create a token is put.

I also tried making a PUT request following the documentation correctly, but the issue persists. It does not accept tokens from Keycloak, nor tokens from the browser inspector with the freshly authenticated user. I don’t understand where the problem lies.

It seems you did not exchange the keycloak token with a Vikunja token?

https://try.vikunja.io/api/v1/docs#tag/auth/operation/get-token-openid

How can I retrieve the authorization code without redirecting the user to obtain it?

You can’t, that’s by design of how oauth works.

1 Like

So how can I use those api? It seems like a bit of a cumbersome process for SSO authentication.

I use a vikunja provided api key through my authentik opened, but the doc @kolaente shared would also work

This provides the login credential token of the user, better definition in the doc.

Looks like it would be


code: “some string”,
redirecturl: “your redirect”,
scope: “Openeid profile email”,

As a POST request will respond with the token you use to authenticate, using:


“Authentication”: “Bearer token provided”,
“Content-Type”: “application/json”

When sending your requests, I can test this tonight when I have we’re acces to my homelab