I'm creating an app that login with Azure AD and the same token returned by Azure AD should be used to consume API's.
I've followed all the steps describe at this article with success and it's working.
My problem is about generate the access-token and refresh-token with a custom scope that allows the app to call the api.
I've tried to react-native libraries and each one has a different problem (probably my code has a problem, not the libraries).
First I've tried react-native-azure-auth and when I've defined the following scope:
scope: ['openid', 'profile', 'User.Read', 'offline_access', 'api://multiplan_broker_dev/read']
When defining this scope, it returns a token only valid for the scope api://multiplan_broker_dev/read and doesn't return a refresh_token (seems it's ignoring the offline_access scope)
You can see the whole returned object here and the main code for this implementation here
The main piece of code is below:
const CLIENT_ID = '75a43463-c3b4-4e31-b740-3a5a1858XXXX';
const TENANT_ID = '862085e1-045e-4d8c-832f-96837b0XXXXX';
const azureAuth = new AzureAuth({
clientId: CLIENT_ID,
tenant: TENANT_ID,
});
export default class Auth0Sample extends Component {
constructor(props) {
super(props);
this.state = { accessToken: null, user: '' , mails: [], userId: ''};
}
_onLogin = async () => {
try {
let tokens = await azureAuth.webAuth.authorize({
scope: ['openid', 'profile', 'User.Read', 'offline_access', 'api://multiplan_broker_dev/read'],
login_hint: 'rioa#XXX.com'
})
console.log('CRED>>>', tokens)
this.setState({ accessToken: tokens.accessToken });
let info = await azureAuth.auth.msGraphRequest({token: tokens.accessToken, path: 'me'})
console.log('info', info)
this.setState({ user: info.displayName, userId: tokens.userId })
} catch (error) {
console.log('Error during Azure operation', JSON.stringify(error))
}
};
So I've tried to another library (react-native-app-auth) and it returns the refresh_token, but ignores completely the scopes I'm asking for.
You can see the returned object here and main code here
What I'm doing wrong?
P.S: The solution can be for any library, I don't have preference.
Thanks
The scope is not correct. Your scope contains both Microsoft Graph API endpint and your own api endpoint. As far as I know we can only ask for the token for one resource at one time.
You can change the scope as below:
scope: ['openid', 'offline_access', 'api://multiplan_broker_dev/read']
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.
I have setup passport and sequelize-typescript for my project. In the passport setup, I use a strategy for example google like this:
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_AUTH_CLIENT_ID,
clientSecret: process.env.GOOGLE_AUTH_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_AUTH_CALLBACK_URL,
profileFields: ['id', 'displayName', 'photos', 'email'],
enableProof: true
},
function(accessToken, refreshToken, profile, done) {
console.log(profile)
const { name, email, picture } = profile._json;
User.findOne({where: {id: profile.id}})
.then(user => {
console.log(user)
if(user === null) {
const { name, email, picture } = profile._json;
// new User({
// id: profile.id,
// name: name,
// email: email,
// pictureUrl: picture,
// })
}
})
done(null, profile)
}
)
)
When I try to use functions such as findOrCreate() or findOne(), I receive a typescript error that says:
[ERROR] 23:29:01 ⨯ Unable to compile TypeScript:
src/passport_strategies.ts:45:18 - error TS2339: Property 'findOne' does not exist on type 'typeof User'.
45 User.findOne({where: {id: profile.id}})
I also get the same error for the part commented out in the first code snippet. The model user I have created is declared like this:
export class User extends Model<User> {} (It has the columns set in the file) Model being imported from sequelize-typescript
Here is where sequelize is created:
export const sequelize = new Sequelize({
"username": c.username,
"password": c.password,
"database": c.database,
"host": c.host,
dialect: 'postgres',
storage: ':memory:',
models: [__dirname + '/models']
});
I tried checking other examples that are on the internet but they all have the same setup and I couldn't figure out why I'm getting this error. Not sure if this helps at all but I'm using postgres dialect.
I suspect that it is a version mismatch.
sequelize-typescript#2 is for sequelize#6.2>= and sequelize-typescript#1 is for sequelize#5>=.
I also suggest for educational purposes to implement typescript with sequelize without the use of the sequelize-typescript package just for understanding the need of the package itself. https://sequelize.org/master/manual/typescript.html
Also just in case with all the respect, i point that #Table is needed if you are using the latest version.
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
I am new to expressJs and PassportJS trying to implement an API which return all basic details for a user i.e
{
isUserLogin: true or false,
allowedActions: [],
applicationUrl: [],
pageComponents: [],
settings: []
}
So the problem that I am facing is if I implement passport.js than guest users are getting unauthorized. Below is my code.
router.js
authRoutes.get('/init', passport.authenticate('bearer', { session: true }), authController.init);
controller.js
exports.init = (req, res) => {
//here is my login to get params. Guest user not able to reach it.
res.status(200).send(params);
};
I think it must be a simple setting at router level but unable to get proper document for this. I am stuck on this for more than 6 days. Any suggestions which could lead me in the right direction will be helpful.
Thanks in advance.
This is what passport-anonymous for.
Install with: npm install passport-anonymous
Then use the strategy
const AnonymousStrategy = require('passport-anonymous').Strategy;
passport.use(new AnonymousStrategy());
Then on your route, pass array of authentication strategies instead of string:
authRoutes.get('/init', passport.authenticate(['bearer', 'anonymous'], { session: true }), authController.init);
And on the controller, you can check whether the user is logged in by checking the req.user object. Keep in mind that you must put anonymous after the bearer in the array
exports.init = (req, res) => {
if (req.user) {
console.log("Logged in");
} else {
console.log("Guest");
}
res.status(200).send(params);
};
I am doing LinkedIn authentication with auth0 in a react app. I have set localhost:3000/upload in callback urls in settings, hopping that after users login at localhost:3000/login, they would be redirected to localhost:3000/upload. However, I always get this error: url localhost:3000/login is not in the list of callback urls. Why would auth0 expect to return to the page where you just logged in after logging in. Shouldn't it be some different url. It just does not make sense to me.
Edit:
export default class AuthService {
constructor(clientId, domain) {
// Configure Auth0
const options = {
allowedConnections: ['linkedin'],
auth: {
params: {responseType: 'code'}
}
};
this.lock = new Auth0Lock(clientId, domain, options)
// Add callback for lock `authenticated` event
this.lock.on('authenticated', this._doAuthentication.bind(this))
// binds login functions to keep this context
this.login = this.login.bind(this)
this.loggedIn = this.loggedIn.bind(this)
}
_doAuthentication(authResult){
// Saves the user token
console.log(authResult);
this.setToken(authResult.idToken)
this.lock.getProfile(authResult.idToken, (error, profile) => {
if (error) {
console.log('Error loading the Profile', error)
} else {
console.log(profile)
}
})
}
//....
Please ensure two things:
1). In your react app code
responseType: 'code'
2). On the Auth0 dashboard, under Settings -> Allowed Callback URLs put your callback entry (localhost:3000/upload) - which I think you have done but just in case.
Let me know if you are still having problems.
Make sure that there is no special hidden characters or space between the commas between the URLs when you paste it into the Auth0 Setting site. I didn't realise about this util I put every urls into Vim to check and see that there are such above cases
In the call to AuthProvider, make sure to use to same callback url as the one in Auth0 settings:
const uri='http://localhost:3000/upload';
<Auth0Provider
domain={domain}
clientId={clientId}
redirectUri={uri}>
To cause a redirect to a different URL after a successful authentication, you need to provide the redirectUrl to Lock, like this:
// Configure Auth0
const options = {
allowedConnections: ['linkedin'],
auth: {
responseType: 'code',
redirectUrl: 'http://localhost:3000/upload'
}
};
this.lock = new Auth0Lock(clientId, domain, options)
(Also notice that the responseType option goes under auth, not under auth.params.)
If you do the redirect, you won't reach the event handler you defined in your login page. You will need to either add an event handler in your destination page (and use responseType:token) or handle authentication results in your server code (this is what you will normally be doing if you are requesting a responseType: code).
the reason why you should set the callback Url in auth0 settings, because any one can use your client id and send request to google or linkedin, get the response to anywhere they set. but with this setting only you can access that response.
once your app is authorized to pull the data from linkedin, linkedin will send the data to where you specified. you should create a page to handle the response from Linkedin server. Let's name that page callback.js and this will be an example of response object.
accessToken: "hNuPLKTZHiE9_lnED0JIiiPNjlicRDp"
appState: null
expiresIn: 7200
idToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRXdSVUl5TURVeE4wSkJPRFZEUlRKRU1EVkZNemsxTXpNNU5VTXlNRGt6T0VWQlJqUkZRUSJ9.eyJodHRwOi8vbG9jYWxob3N0OjMwMDAvcm9sZSI6InNpdGVPd25lciIsImdpdmVuX25hbWUiOiJvbWFyIiwiZmFtaWx5X25hbWUiOiJpYm8iLCJuaWNrbmFtZSI6Im9tYXJpYm8xOTgyIiwibmFtZSI6Im9tYXIgaWJvIiwicGljdHVyZSI6Imh0dHBzOi8vbGg1Lmdvb2dsZXVzZXJjb250BQUFBQUkvQUFBQUFBQUFBQUEvQUNIaTNyLTEwLTEyVDIyOjU4OjAxLjgzM1oiLCJpc3MiOiJodHRwczovL3BvcnRmb2xpby15aWxtYXouYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTE0MDY0NTA2ODI2OTgwNTA5ODY3IiwiYXVkIjoiUEdVY242RjRRS21PRkJhb1k0UFdCeWpjVzIyT09vNGMiLCJpYXQiOjE1NzA5MjEwODIsImV4cCI6MTU3MDk1NzA4MiwiYXRfaGFzaCI6InN0R1l5SnJaMHNnbVYzSWNLWjlPeFEiLCJub25jZSI6InRrOV95b096enRmVThVVjFVMlVFR3IyMW5ORW5abjk4In0.TYS7mM8N2d7jEHFdWQGTSeAAUaDt4-0SMUG3LrcQ1r3xzY0RMGsUsEszj5xqk1GE0cIlFS10xCOYKsuHSwsFLomC1EbLjntjkledHtfD0MW84cMoXN6a-x-1-bNwl3lMYJ98qklTrNvTvkQJ6DWhei3hJ8rs8dnbNyCfckNVU6ptJU-9ef1DwWfHRomW5LQ6WSDRHZScW697gdgBEMU-Nd2SddyHhQe0kVh6lKdcbnskEAyCJLE07jfM40RQI_8LJouFcpoyImcXSDZlKv90tYfVDq9_TwE3GNaSz5I5snn0457oCgz0vuX0JoCUiaDuTIX7XiyXnozW_DxGMuhk4w"
idTokenPayload: {http://localhost:3000/role: "siteOwner", given_name: "me", family_name: "you", nickname: "nck", name: "nm", …}
refreshToken: null
scope: null
state: "xkEbffzXbdOYPLkXOUkrQeb0Jysbnlfy"
tokenType: "Bearer"
//THIS CODE IS FOR NEXT.JS9
//auth.js
class Auth0 {
constructor() {
this.auth0 = new auth0.WebAuth({
domain: "portfolio-ys.auth0.com",
clientID: "PGUWJQKmOFBaoY4PWByjcW22OOo4c",
redirectUri: "http://localhost:3000/callback",
responseType: "token id_token",
scope: "openid profile"
});
this.handleAuthentication = this.handleAuthentication.bind(this);
}
//there are too many methods are defined here i put only relevant ones
handleAuthentication() {
return new Promise((resolve, reject) => {
this.auth0.parseHash((err, authResult) => {
console.log(authResult);
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
resolve();
} else if (err) {
reject(err);
}
});
});
}
setSession function is where you set the cookies based on response object. I use js-cookie package to set the cookie.
setSession(authResult) {
const expiresAt = JSON.stringify(
authResult.expiresIn * 1000 + new Date().getTime()
);
Cookies.set("user", authResult.idTokenPayload);
Cookies.set("jwt", authResult.idToken);
Cookies.set("expiresAt", expiresAt);
}
}
const auth0Client = new Auth0();
export default auth0Client;
callback.js
import React from "react"
import auth0Client from "./auth0"
import {withRouter} from "next/router"
class Callback extends React.Component{
async componentDidMount(){
await auth0Client.handleAuthentication()
this.props.router.push('/')
}
render() {
return (
<h1>verifying logging data</h1>
)
}
}
export default withRouter(Callback) //this allows us to use router
I had similar issue "callback URL mismatch" and resolved it by running the application over https with a trusted certificate.
Here is a snippet from Auth0 applications settings section about callback URL, which says "Make sure to specify the protocol (https://) otherwisw the callback may fail in some cases."
If you're using the Android(Kotlin) SDK of auth0, I noticed that during runtime, the requested URL is being changed. e.g. app://{your_auth0_domain}/android/{package_name}/callback://{your_auth0_domain}/android/app://{your_auth0_domain}/android//callback
Originally URL was
app://{your_auth0_domain}/android/{package_name}/callback
and SDK is appending "://{your_auth0_domain}/android/app://{your_auth0_domain}/android//callback" this extra part.
Solution: Either put the same URL in auth0 setting dashboard as it showing in your logs
or
WebAuthProvider
.login(account)
.withScheme("app") // instead of complete URL, put only the remaining part from the URL,
.start(this, object : Callback<Credentials, AuthenticationException> {}
I hope it will definitely help android/app developer.