I have a NuxtJS (v2.15.8) app that needs to consume an API on GCP App Engine (NodeJS) protected by IAP. I'm trying to get an access token for the Service Account I've created as described here: https://cloud.google.com/iap/docs/authentication-howto
I've searched a lot but I can't find on Google nobody with the same or similar need.
After several attempts with different methods, now I'm trying to sign a JWT in my code in order to get the access token from the API.
To do this I'm using functions from cryptojs lybrary to create the signature, but I have a problem with the sign() function. It gives me this error:
TypeError: Cannot read properties of null (reading '2')
at module.exports (fixProc.js?4dd0:14:1)
at parseKeys (index.js?2aee:19:1)
at sign (sign.js?6fe7:11:1)
at Sign.signMethod [as sign] (index.js?b692:42:1)
at _callee2$ (auth.js?889e:78:1)
at tryCatch (runtime.js?96cf:63:1)
at Generator.invoke [as _invoke] (runtime.js?96cf:294:1)
at Generator.eval [as next] (runtime.js?96cf:119:1)
at asyncGeneratorStep (asyncToGenerator.js?1da1:3:1)
at _next (asyncToGenerator.js?1da1:25:1)
Here's the code I'm using
const qs = require('qs');
const crypto = require('crypto');
const private_key_id=options.private_key_id
const client_email=options.client_email
const private_key=options.private_key
const issued_at=Math.round(+new Date()/1000);
const expires_at=issued_at+3600
const header="{'alg':'RS256','typ':'JWT','kid':'"+private_key_id+"'}"
const header_base64=Buffer.from(header).toString('base64')
const body="{'iss':'"+client_email+"','aud':'"+oauth_token_uri+"','exp':"+expires_at+",'iat':"+issued_at+",'sub':"+client_email+",'target_audience':'"+iap_client_id+"'}"
const body_base64=Buffer.from(body).toString('base64')
const sign = crypto.createSign('sha256');
sign.write(Buffer.from(`${header_base64}.${body_base64}`));
sign.end();
const signature_base64=sign.sign(private_key,'base64');
const assertion=Buffer.from(`${header_base64}.${body_base64}.${signature_base64}`)
axios({
method: 'post',
url: 'https://www.googleapis.com/oauth2/v4/token',
data: qs.stringify({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: assertion
}),
headers: {
'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
}
}).then(function (response) {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});
All the variables used are taken from the service account credentials JSON file and set as env variables in my app.
I solved using
const sign = crypto.createSign('RSA-SHA256');
instead of
const sign = crypto.createSign('sha256');
Related
Im wondering if anyone has some experience in this issue.
I am getting a state mismatch error when trying to register using react native app auth.
React native version: "0.67.3",
React native app auth version: "^6.4.3",
[Error: State mismatch, expecting Z2-6m8_T7FcIlbG9wep3Xb2wvgsylbd9M54iiX97rXs but got Z2-6m8_T7FcIlbG9wep3Xb2wvgsylbd9M54iiX97rXsregistration in authorization response <OIDAuthorizationResponse: 0x6000017b29e0, authorizationCode: 4d890080dde715cedddf076e5ffb4fc8aaeeb22d4ebca281d4c7d74df377607c, state: "Z2-6m8_T7FcIlbG9wep3Xb2wvgsylbd9M54iiX97rXsregistration", accessToken: "(null)", accessTokenExpirationDate: (null), tokenType: (null), idToken: "(null)", scope: "(null)", additionalParameters: {
}, request: <OIDAuthorizationRequest: 0x600001f7c000, request: https://api.staging.com/oauth/authorize?nonce=iJxSOkt6tGToBUndfg3n0V4B_ZZNBIm8TwbTg18EGOo&response_type=code&scope=trusted%20public%20refresh_token&code_challenge=iNlpVkj7UDpXyu5wBlMuln41huSZcGsdWEQ9fYLtcuU&code_challenge_method=S256&redirect_uri=someredirectt&client_id=9dc36c26d21198f5c97f12b34be3cce7a37e5abdc323fcc0b205a898d22994f7&state=Z2-6m8_T7FcIlbG9wep3Xb2wvgsylbd9M54iiX97rXs>>]
the code that generates the request is so:
import { authorize } from 'react-native-app-auth';
const config = {
issuer: TEMP_API,
clientId: OAUTH_PUBLIC_CLIENT_ID,
redirectUrl: OAUTH_CALLBACK_URL,
clientSecret: OAUTH_CLIENT_SECRET,
scopes: ['trusted', 'public', 'refresh_token'],
};
const configForSignup = {
...config,
additionalParameters: {
response_mode: 'query'
},
};
export const authorizeOauthUser = (
{
isSignup,
},
) => async () => {
try {
const oAuthConfig = isSignup ? configForSignup : config;
const result = await authorize(oAuthConfig);
console.log({result});
} catch (error) {
console.log({error})
}
};
I have looked at the following ticket and implemented the suggested response_mode: "query" but to no avail.
Im not quite sure of the 'registration' at the end of the expected token [Z2-6m8_T7FcIlbG9wep3Xb2wvgsylbd9M54iiX97rXsregistration] is appended to the token itself and thats why its mismatching or if its just spaced strangely.
A little bit old but the response mentions explicitly state: "Z2-6m8_T7FcIlbG9wep3Xb2wvgsylbd9M54iiX97rXsregistration"
I guess you are trying to connect to a custom authentication system, you should log that as a bug. The state parameter in the response should exactly match the one from the request.
The state is not the token, it is just a random ID to make sure that the response is received for the right request.
On my app I have installed Vuex-oidc package to implement the authentication using the endpoints that I have got from the backend, and everything is working well on my machine, but I got the request to modify the oidc settings because right now the auth is working only from locale, and is not working from the development server, so I was requested to move these settings from my config/oidc.js file to the Nuxt "runtime".
Here is my config/oidc.js file:
export const oidcSettings = {
authority: 'https://***/***.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_***_SIGNUPORSIGNIN',
clientId: '************',
token_endpoint: 'https://***/***.onmicrosoft.com/oauth2/v2.0/token?p=B2C_***_SIGNUPORSIGNIN',
redirectUri: 'http://localhost:3000/oidc-test/oidc-callback',
responseType: 'id_token ',
scope: 'https://***.onmicrosoft.com/***/Read openid'
}
Then I have this oidc.js file in the store which is using my oidcSettings:
import { vuexOidcCreateStoreModule } from 'vuex-oidc'
import { oidcSettings } from '~/config/oidc'
const storeModule = vuexOidcCreateStoreModule(
oidcSettings,
{
namespaced: true,
dispatchEventsOnWindow: true,
publicRoutePaths: ['/', 'oidc-callback-error']
},
// Optional OIDC event listeners
{
userLoaded: user => console.log('OIDC user is loaded:', user),
userUnloaded: () => console.log('OIDC user is unloaded'),
accessTokenExpiring: () => console.log('Access token will expire'),
accessTokenExpired: () => console.log('Access token did expire'),
silentRenewError: () => console.log('OIDC user is unloaded'),
userSignedOut: () => console.log('OIDC user is signed out')
}
)
export const state = () => (storeModule.state)
export const getters = storeModule.getters
export const actions = storeModule.actions
export const mutations = storeModule.mutations
I am not sure what I am supposed to use or to do for accomplish the request, I was thinking to introduce a .env and retrieve oidcSetting from ther. Can it be a good idea?
I can also see that Nuxt has his own runtimes which can be declared in nuxt.config.js, such as "publigRuntimeConfig: {}" and "privateRuntimeConfig: {}", but I cannot find much on it, just the basic way to use them, but I am no sure which of my settings should be private and which should be public, and also I am not sure how to call them in my store/oidc.js file.
Some suggestion?
Do you have these settings
export const oidcSettings = {
authority: 'https://your_oidc_authority',
clientId: 'your_client_id',
redirectUri: 'http://localhost:1337/oidc-callback',
responseType: 'id_token token',
scope: 'openid profile'
}
I can only suggest to visit https://github.com/perarnborg/vuex-oidc/wiki
I am not sure this is the best practice to get the result I want, but it works:
What I have done is to use an .env file and hold all the informations there:
AUTHORITY = https://***/***.onmicrosoft.com/v2.0/.well-known/...
CLIENT_ID = ************
TOKEN_ENDPOINT = https://***/***.onmicrosoft.com/oauth2/v2.0/token?...
REDIRECT_URI = http://localhost:3000/oidc-test/oidc-callback
RESPONSE_TYPE = id_token
SCOPE = https://***.onmicrosoft.com/***/Read openid
Then in my nuxt.config.js I have added require('dotenv').config() in the very top of the file (#nuxtjs/dotenv and dotenv packages must be installed and #nuxtjs/dotenv has to be declared in the modules and in the buildModules in your nuxt.config.js)
Now you can delete the config/oidc.js file and you can edit the store/oidc.js:
Instead of having:
import { oidcSettings } from '~/config/oidc'
You can write:
const oidcSettings = {
authority: process.env.AUTHORITY,
clientId: process.env.CLIENT_ID,
token_endpoint: process.env.TOKEN_ENDPOINT,
redirectUri: process.env.REDIRECT_URI,
responseType: process.env.RESPONSE_TYPE,
scope: process.env.SCOPE
}
I have some problems with authentication with Google OAuth2 in my react-native app. I'm using 'expo-auth-session' library for my authentification. I need get access token and then get Youtube profile. But i'm stuck with error "Invalid parameter value for redirect_uri: Invalid scheme"
My scheme in app.json:
"scheme": "com.liga.online"
My code is below:
import {
makeRedirectUri,
useAuthRequest,
ResponseType,
} from "expo-auth-session";
const discoveryYoutube = {
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenEndpoint: 'https://oauth2.googleapis.com/token',
revocationEndpoint: 'https://oauth2.googleapis.com/revoke'
};
/// Inside my React component
const [requestYoutube, responseYoutube, promptAsyncYoutube] = useAuthRequest(
{
responseType: ResponseType.Code,
clientId: YOUTUBE_CLIENT_ID,
scopes: ["https://www.googleapis.com/auth/youtube.readonly"],
redirectUri: makeRedirectUri({
native: "com.liga.online/callback",
}),
},
discoveryYoutube
);
When I press the button, callback is starting
const signInYoutube = async () => {
const response = await promptAsyncYoutube();
console.log(response.data);
}
But I get error
Any idea how I can fix it?
P.S. I tried fix it with library "expo-google-app-auth". I get access token, but when I try to get Youtube profile and get "Request failed with status code 403".
UPDATE 1
By the way about my connection to Youtube Profile.
I change something to get access token.
For example:
import * as Google from 'expo-google-app-auth';
import { startAsync } from 'expo-auth-session';
// Inside my React component
// When I press the button, callback is starting
const signInYoutube = async () => {
const config = {
androidClientId: YOUTUBE_CLIENT_ID
};
const { type, accessToken, user } = await Google.logInAsync(config);
// I need again open my Browser for get Youtube data
const response = await startAsync({
authUrl: `https://www.googleapis.com/youtube/v3/channels?access_token=${accessToken}&part=snippet&mine=true&scope=https://www.googleapis.com/auth/youtube.readonly`,
showInRecents: true
});
console.log(response.data);
}
But I get error
UPDATE 2
I wanted to see which data is loaded from AuthRequest. And I see pretty weird log. Redirect_uri is different from the set.
RESOLUTION
When I add "https://www.googleapis.com/auth/youtube.readonly" in my scopes - i can get profile data. Another words below is my code.
import axios from 'axios';
import * as Google from 'expo-google-app-auth';
// Inside my React component
// Callback function
const signInYoutube = async () => {
const config = {
androidClientId: YOUTUBE_CLIENT_ID,
scopes: ['https://www.googleapis.com/auth/youtube.readonly']
};
const { type, accessToken, user } = await Google.logInAsync(config);
if (type === 'success') {
const response = await axios.get(
`https://www.googleapis.com/youtube/v3/channels?part=id&mine=true&key=${encodeURI(YOUTUBE_API_KEY)}`,
{
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
setYoutubeData({ accessToken, user, youtubeId: response.data.items[0].id });
}
};
IMPORTANT
Don't remember add in your project Youtube API v3
It looks like your expo environment is using the development redirect URI instead of the native one. Check out these docs for setting up the environment that will give you the native scheme you're looking for: https://docs.expo.io/guides/authentication/#standalone-bare-or-custom
Also make sure that you register your custom scheme with Google: https://developers.google.com/identity/protocols/oauth2/native-app#redirect-uri_custom-scheme
As for your Youtube example, you should be specifying the scopes in the call to Google.loginAsync, not the call to the Youtube API. scopes are requested during the authorization step, and your current authorization request doesn't include any. The relevant docs are here: https://docs.expo.io/versions/latest/sdk/google/#loginasync
REDIRECT URI
Your code looks roughly right though your redirect URI is a private URI scheme and should include a colon character:
com.liga.online:/callback
TRACING MESSAGES
For people to help you with your YouTube profile request you'll need to be able to tell us what is being sent over HTTP/S. Could you try to trace messages as in this write up of mine, then paste the request and response details into your question above
In my vue/cli 4/vuex / vue-resource project I read data from protected area with passport from backend api as in routes/api.php:
Route::group(['middleware' => 'auth:api', 'prefix' => 'adminarea', 'as' => 'adminarea.'], function ($router) {
...
Route::get('dashboard', 'API\Admin\DashboardController#index');
I found how to set access token at
vue-resource interceptor for auth headers
and in src/main.js I added method :
Vue.http.interceptors.push((request, next) => {
/*
console.log('request::')
console.log(request)
console.log('this::')
console.log(this)
console.log('Vue::')
console.log(Vue)
console.log('Vue.$store::')
console.log(Vue.$store)
console.log('Vue.$store.getters.token::')
console.log(Vue.$store.getters.token)
*/
request.headers.set('Authorization', 'Bearer XXX key')
request.headers.set('Accept', 'application/json')
next()
})
and it works for me if I fill bearer token manually, but I did not find how to read it from store?
In my code above I failed to read $store.getters value...
Which is the valid way ?
UPDATED BLOCK # 1:
If in src/main.js I comment line :
import store from './store'
and add line :
import store from './store.js';
then in console I got error :
ERROR Failed to compile with 1 errors 12:55:54 PM
This relative module was not found:
* ./store.js in ./src/main.js
But with
import store from './store'
I failed get access to store data
Thanks!
Try importing your Vuex file instead of trying to access the $store via the Vue instance.
import store from './store.js';
Vue.http.interceptors.push((request, next) => {
request.headers.set('Authorization', `Bearer ${store.getters.token}`)
request.headers.set('Accept', 'application/json')
next()
})
I found decision in writing token into localStorage on login and
reading this value from ue.http.interceptors.push((request, next) => {
let token = localStorage.getItem('token')
request.headers.set('Authorization', 'Bearer '+token)
request.headers.set('Accept', 'application/json')
next()
})
That works for me!
In my authority system I store secret token in Vue fields and get it from localStorage in created event:
const app = new Vue({
router: router,
data: {
token: '',
user: null,
},
created: function () {
var token = localStorage.getItem('token');
if (token) {
this.token = token;
}
...
Then I'm trying to fetch token in beforeRouteEnter method of compomnent:
beforeRouteEnter: function (to, from, next) {
var id = to.params.id;
console.log(router.app.$root.token);
},
But the field is empty, however it defined later. How to correct structure of my application to send api requests with token?
You can pass a callback to next, which will have access to the instance of the Vue component being routed to.
From the documentation:
The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument
In your case, it would look like this:
beforeRouteEnter: function (to, from, next) {
var id = to.params.id;
next((vm) => {
console.log(vm.$root.token);
})
},
Now, is this the correct structure for your application to send api requests with this token? That's hard to say without more context and probably too broad. But I'd think you could store the token in whatever library you're using for api requests and that'd be better than referencing it each time the route changes.