NestJS Swagger oAuth2 (oAuth0) to bearer token in header - oauth-2.0

this is my code:
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerCustomOptions, SwaggerModule } from '#nestjs/swagger';
import * as config from 'config';
import * as crypto from 'crypto';
async function bootstrap() {
const serverConfig = config.get('conf');
console.log(`Environment:` + serverConfig.env);
console.log(`Running Port:` + serverConfig.server.port);
const teanat = 'MY_AUTH0_DOMAIN'
const app = await NestFactory.create(AppModule);
const nonce = crypto.randomBytes(16).toString('base64');
const docBuilderConfig = new DocumentBuilder()
.setTitle('Awesome Middleware')
.setDescription('Represents the middleware api services')
.setVersion('1.0')
.addOAuth2(
{
type: 'oauth2',
flows: {
implicit: {
tokenUrl: `${teanat}/oauth/token`,
authorizationUrl: `${teanat}/authorize?audience=${`${teanat}/api/v2/`}&nonce=${nonce}`,
scopes: {} // { openid: openid, ... }
},
},
},
)
.build()
const document = SwaggerModule.createDocument(app, docBuilderConfig);
const swaggerCustomOptions: SwaggerCustomOptions = {
'customSiteTitle': 'Middle Api',
'explorer': true,
'swaggerOptions': {
persistAuthorization: true,
oauth2RedirectUrl: 'http://localhost:4200/',
oauth: {
clientId: 'CLIENT_ID',
}
}
}
SwaggerModule.setup('/', app, document, swaggerCustomOptions);
app.enableCors();
app.use((req, res, next) => {
res.header("X-powered-by", "My Company");
res.header("Server", "My Server");
next();
});
await app.listen(4200);
}
bootstrap();
I go to the login page just like in the client web app
and then the token return on the URL prams which I redirect to
(localhost:4200?token_access=MY_YOKEN)
I want this token to be injected to the header where the control use this auth2 with the same auth name

Related

Capacitor iOS Using Cookie Based Auth

I am using Capacitor v3, NextJS static export, and a Django backend to build out an iOS app based on a production website.
The current backend authentication scheme uses Django sessions via cookies as well as setting the CSRF token via cookies. The CSRF token can be bypassed pretty easily for the app and not worried about disabling that but forking our authentication scheme would be somewhat of a hassle. The capacitor-community/http claims to allow Cookies but I haven't been able to configure that correctly.
Capacitor Config:
import { CapacitorConfig } from '#capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.nextwebapp.app',
appName: 'nextwebapp',
webDir: 'out',
bundledWebRuntime: false
};
export default config;
Note that I have tried setting server.hostname to myapp.com as well.
Based on the comments at the bottom of the capacitor http readme I set the following Info.plist values.
App/Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
....
<key>WKAppBoundDomains</key>
<array>
<string>staging.myapp.com</string>
<string>myapp.com</string>
</array>
</dict>
</plist>
The web app uses a react hooks wrapper package for axios so in order to keep changes minimal I made a hook that mimics the state returned from that package.
hooks/useNativeRequest.ts
import { useEffect, useState } from "react";
import { Http } from "#capacitor-community/http";
import {
BASE_URL,
DEFAULT_HEADERS,
HOST_NAME,
ERROR_MESSAGE,
Refetch,
RequestOptions,
ResponseValues,
RequestConfig,
} from "#utils/http";
import { handleResponseToast } from "#utils/toast";
const makeUrl = (url): string => `${BASE_URL}${url}`;
const getCSRFToken = async () =>
await Http.getCookie({ key: "csrftoken", url: HOST_NAME });
const combineHeaders = async (headers: any) => {
const newHeaders = Object.assign(DEFAULT_HEADERS, headers);
const csrfHeader = await getCSRFToken();
if (csrfHeader.value) {
newHeaders["X-CSRFToken"] = csrfHeader.value;
}
return newHeaders;
};
function useNativeRequest<T>(
config?: RequestConfig,
options?: RequestOptions
): [ResponseValues<T>, Refetch<T>] {
const [responseState, setResponseState] = useState({
data: null,
error: null,
loading: false,
});
let method = "get";
let url = config;
let headers = {};
let params = undefined;
let data = undefined;
if (config && typeof config !== "string") {
url = config.url;
method = config.method?.toLowerCase() ?? method;
headers = config.headers;
params = config.params;
data = config.data;
}
const requestMethod = Http[method];
const makeRequest = async () => {
setResponseState({ error: null, data: null, loading: true });
try {
const reqHeaders = await combineHeaders(headers);
console.log({
url,
reqHeaders,
params,
data
})
const response = await requestMethod({
url: makeUrl(url),
headers: reqHeaders,
params,
data,
});
if (response?.status === 200) {
setResponseState({ error: null, data: response.data, loading: false });
handleResponseToast(response?.data?.detail);
} else {
const errorMessage = response?.data?.detail || ERROR_MESSAGE;
handleResponseToast(errorMessage);
setResponseState({
error: errorMessage,
data: response.data,
loading: false,
});
}
return response;
} catch {
setResponseState({
error: ERROR_MESSAGE,
data: null,
loading: false,
});
return Promise.reject(ERROR_MESSAGE);
}
};
useEffect(() => {
if (!options?.manual) {
makeRequest();
}
}, [options?.manual]);
return [responseState, makeRequest];
}
export { useNativeRequest };
The console.log above never includes the additional csrf cookie and in the getter logs it doesn't contain a value.
Backend Django
MIDDLEWARE = [
...
'myapp_webapp.middle.CustomCSRFMiddleWare',
]
CORS_ALLOWED_ORIGINS = [
...
"capacitor://localhost",
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
}
middleware
class CustomCSRFMiddleWare(CsrfViewMiddleware):
def process_request(self, request):
# Special Processing for API Requests
if "/api/v1" in request.path:
try:
requested_with = request.headers['X-Requested-With']
myapp_request = request.headers['X-Myapp-Request']
# Check Custom Headers
if not (requested_with == 'XMLHttpRequest' and myapp_request == '1'):
raise PermissionDenied()
return None
except KeyError:
# All API Requests should include the above headers
raise PermissionDenied()
# Call original CSRF Middleware
return super(CustomCSRFMiddleWare, self).process_request(request)
Occasionally the backend will also show that X-Requested-With is not being sent but it is included in the DEFAULT_HEADERS constant I have in the UI and appears in the console.log.
Is anything above preventing me from being able to read and send cookies from Capacitor on iOS? Does Cookie based authentication even work with capacitor?
Here is my updated react hook that combine's my above question and thread mentioned in the comments as well as some manual cookie setting.
The below client side code worked without changes to existing Django Session authentication.
The changes from my code above
Added credentials: "include" to webFetchExtra
Added "Content-Type": "application/json" to headers
Handle override of the initial config for manual request & refetch
Set Session Cookie After Response
Based on the docs this shouldn't be necessary but I am keeping in my code for now.
import { useCallback, useEffect, useState } from "react";
import { AxiosRequestConfig } from "axios";
import { Http } from "#capacitor-community/http";
const DEFAULT_HEADERS = {
"X-Requested-With": "XMLHttpRequest",
"X-MyApp-Request": "1",
"Content-Type": "application/json",
};
const makeUrl = (url): string => `${BASE_URL}${url}`;
const getCSRFToken = async () =>
await Http.getCookie({ key: "csrftoken", url: HOST_NAME });
const setSessionCookie = async () => {
const sessionId = await Http.getCookie({ key: "sessionid", url: HOST_NAME });
if (sessionId.value) {
await Http.setCookie({
key: "sessionid",
value: sessionId.value,
url: HOST_NAME,
});
}
};
const combineHeaders = async (headers: any) => {
const newHeaders = Object.assign(DEFAULT_HEADERS, headers);
const csrfHeader = await getCSRFToken();
if (csrfHeader.value) {
newHeaders["X-CSRFToken"] = csrfHeader.value;
}
return newHeaders;
};
const parseConfig = (config: RequestConfig, configOverride?: RequestConfig) => {
let method = "get";
let url = config;
let headers = {};
let params = undefined;
let data = undefined;
if (config && typeof config !== "string") {
url = config.url;
method = config.method ?? method;
headers = config.headers;
params = config.params;
data = config.data;
}
return {
url,
method,
headers,
params,
data,
...(configOverride as AxiosRequestConfig),
};
};
function useNativeRequest<T>(
config?: RequestConfig,
options?: RequestOptions
): [ResponseValues<T>, Refetch<T>] {
const [responseState, setResponseState] = useState({
data: null,
error: null,
loading: false,
});
const makeRequest = useCallback(
async (configOverride) => {
setResponseState({ error: null, data: null, loading: true });
const { url, method, headers, params, data } = parseConfig(
config,
configOverride
);
try {
const reqHeaders = await combineHeaders(headers);
const response = await Http.request({
url: makeUrl(url),
headers: reqHeaders,
method,
params,
data,
webFetchExtra: {
credentials: "include",
},
});
if (response?.status === 200) {
setResponseState({
error: null,
data: response.data,
loading: false,
});
await setSessionCookie();
} else {
setResponseState({
error: errorMessage,
data: response.data,
loading: false,
});
}
return response;
} catch {
setResponseState({
error: ERROR_MESSAGE,
data: null,
loading: false,
});
return Promise.reject(ERROR_MESSAGE);
}
},
[config]
);
useEffect(() => {
if (!options?.manual) {
makeRequest(config);
}
}, [options?.manual]);
return [responseState, makeRequest];
}
export { useNativeRequest };

Loopback 4 implementing Microsoft Graph API

I am currently building a microservice that is responsible to communicate with Microsoft Graph, I have already made one with Loopback 3 and this was not a problem.
Except now, I am trying to do the same thing but with Loopback 4, but since the language changes from JavaScript to TypeScript I don't know if it's still possible to achieve this.
This was the code I used for Loopback 3 in my root server file:
'use strict';
const express = require('express');
const erouter = require('express').Router();
var session = require('express-session');
var passport = require('passport');
var OIDCStrategy = require('passport-azure-ad').OIDCStrategy;
const request = require('request');
var querystring = require('querystring');
const graph = require('./graph.service');
const getBookings = require('./getBookings.service');
const cors = require('cors');
var compression = require('compression');
module.exports = function(server) {
// Install a `/` route that returns server status
var router = server.loopback.Router();
router.get('/', server.loopback.status());
// Configure simple-oauth2
const oauth2 = require('simple-oauth2').create({
client: {
id: process.env.OAUTH_APP_ID,
secret: process.env.OAUTH_APP_PASSWORD
},
auth: {
tokenHost: process.env.OAUTH_AUTHORITY,
authorizePath: process.env.OAUTH_AUTHORIZE_ENDPOINT,
tokenPath: process.env.OAUTH_TOKEN_ENDPOINT
}
});
passport.serializeUser(function(user, done) {
var MSUser = server.models.MSUser;
var id = user.profile.oid;
MSUser.find({ where: { oid: id } }, function(err, msu) {
if (err) return done(err, null);
if (!msu) {
MSUser.create(user);
} else {
done(null, id);
}
});
});
passport.deserializeUser(function(id, done) {
var MSUser = server.models.MSUser;
MSUser.findById(id, function(err, user) {
if (err) return next(err);
done(null, user);
});
});
async function signInComplete(iss, sub, profile, accessToken, refreshToken, params, done) {
if (!profile.oid) {
return done(new Error("No OID found in user profile."), null);
}
try {
const user = await graph.getUserDetails(accessToken);
if (user) {
profile['email'] = user.mail ? user.mail.toLowerCase() : user.userPrincipalName.toLowerCase();
}
} catch (err) {
done(err, null);
}
let oauthToken = oauth2.accessToken.create(params);
var AuthUser = server.models.AuthUser;
var user = {};
AuthUser.find({ where: { email: profile['email'] } }, function(err, au) {
if (err) return done(err, null);
if (au.length != 1) return done(new Error("User was not found with that email address."), null);
user = au[0];
const dataMsAuth = querystring.stringify({
"created": new Date().toDateString(),
"token_type": oauthToken.token.token_type,
"expires_in": oauthToken.token.expires_in,
"access_token": oauthToken.token.access_token,
"scope": oauthToken.token.scope,
"ext_expires_in": oauthToken.token.ext_expires_in,
"refresh_token": oauthToken.token.refresh_token,
"id_token": oauthToken.token.id_token,
"expires_at": new Date(oauthToken.token.expires_at).toDateString()
});
const postMSAuth = {
url: process.env.API_URL + "api/Companies/" + user.companyId + "/msauth",
method: 'POST',
body: dataMsAuth,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
request(postMSAuth, function(err, resp, body) {
if (err) return done(err, null);
var MSUser = server.models.MSUser;
var id = profile.oid;
var msuser = { profile, oauthToken, oid: id, email: profile.email }
MSUser.findById(id, function(err, msu) {
if (err) return done(err, null);
if (!msu) {
MSUser.create(msuser);
}
});
return done(null, msuser);
});
});
}
passport.use(new OIDCStrategy({
identityMetadata: `${process.env.OAUTH_AUTHORITY}${process.env.OAUTH_ID_METADATA}`,
clientID: process.env.OAUTH_APP_ID,
responseType: 'code id_token',
responseMode: 'form_post',
redirectUrl: process.env.OAUTH_REDIRECT_URI,
allowHttpForRedirectUrl: true,
clientSecret: process.env.OAUTH_APP_PASSWORD,
validateIssuer: false,
passReqToCallback: false,
scope: process.env.OAUTH_SCOPES.split(' ')
},
signInComplete
));
var app = express();
app.use(compression());
app.use(session({
secret: process.env.BOOKINGS_LOOPBACK_SECRET,
resave: false,
saveUninitialized: false,
unset: 'destroy'
}));
app.use("/result", express.static('client'));
app.use(passport.initialize());
app.use(passport.session());
app.use(cors({
origin: '*'
}));
erouter.get('/API/bookings/:companyid', getBookings());
erouter.get('/auth/signin',
function(req, res, next) {
passport.authenticate('azuread-openidconnect', {
response: res,
prompt: 'login',
state: req.query.state,
failureRedirect: process.env.WEBSITE_URL + 'settings?error=incorrect_request',
successRedirect: process.env.WEBSITE_URL + 'settings?auth=success'
})(req, res, next);
}
);
erouter.post('/auth/callback',
function(req, res, next) {
passport.authenticate('azuread-openidconnect', {
response: res,
failureRedirect: process.env.WEBSITE_URL + 'settings?error=permission_denied',
successRedirect: process.env.WEBSITE_URL + 'settings?auth=success'
})(req, res, next);
}
);
app.use(erouter);
server.use(app);
server.use(router);
};
So my question is: "Is it possible to implement Microsoft Graph API in TypeScript using Loopback 4 or should I keep using Loopback 3 In JavaScript?"
Thanks in advance,
Billy Cottrell

How to refresh access token in electron app?google oauth2.0

I use this api to provide google login function for my electron app
https://github.com/googleapis/google-auth-library-nodejs
My access token expires after 3600 seconds
I don’t want my users to log in again after 3600 seconds
How can I make the token refresh automatically?
I try to use the document example code on the my app
But it doesn't seem to work
How can I get a new access_token
I try the code below to get a new access_token
But nothing happens
const { app, BrowserWindow, screen } = require('electron');
const fs = require('fs');
const { google } = require('googleapis'); // auth node js
googleOAuth2Login();
function googleOAuth2Login() {
const SCOPES = ['https://www.googleapis.com/auth/drive'];
const TOKEN_PATH = 'token.json';
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
authorize(JSON.parse(content), showAccessToken);
});
function authorize(credentials, callback) {
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id,
client_secret,
redirect_uris[0]
);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, content) => {
if (err) return getAccessToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(content));
callback(JSON.parse(content))
oAuth2Client.on('tokens', (tokens) => {
//this handle not work
if (tokens.refresh_token) {
// store the refresh_token in my database!
console.log(tokens.refresh_token);
}
console.log(tokens.access_token);
});
});
}
/**
* This method opens a new window to let users log-in the OAuth provider service,
* grant permissions to OAuth client service (this application),
* and returns OAuth code which can be exchanged for the real API access keys.
*
* #param {*} interactionWindow a window in which the user will have interaction with OAuth provider service.
* #param {*} authPageURL an URL of OAuth provider service, which will ask the user grants permission to us.
* #returns {Promise<string>}
*/
function getOAuthCodeByInteraction(interactionWindow, authPageURL) {
interactionWindow.loadURL(authPageURL, { userAgent: 'Chrome' });
return new Promise((resolve, reject) => {
const onclosed = () => {
reject('Interaction ended intentionally ;(');
};
interactionWindow.on('closed', onclosed);
interactionWindow.on('page-title-updated', (ev) => {
const url = new URL(ev.sender.getURL());
// console.log(url.searchParams)
if (url.searchParams.get('approvalCode')) {
console.log('allow')
interactionWindow.removeListener('closed', onclosed);
interactionWindow.close();
return resolve(url.searchParams.get('approvalCode'));
}
if ((url.searchParams.get('response') || '').startsWith('error=')) {
console.log('reject')
interactionWindow.removeListener('closed', onclosed);
interactionWindow.close();
return reject(url.searchParams.get('response'));
}
});
});
};
function executeAuthWindow(authWindow, authUrl) {
authWindow.setMenu(null);
authWindow.show();
return new Promise((resolve, reject) => {
getOAuthCodeByInteraction(authWindow, authUrl)
.then((res) => {
if (res != 'Interaction ended intentionally ;(') {
return resolve(res);
}
if (res == 'Interaction ended intentionally ;(') {
return reject('Fail:Authorization window colose');
}
}).catch((err) => {
if (err = 'error=access_denied') {
return reject('Fail: error=access_denied');
}
});
})
}
function getAccessToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
const authWindow = new BrowserWindow({
width: 600,
height: 800,
show: false,
'node-integration': false,
'web-security': false
});
executeAuthWindow(authWindow, authUrl)
.then((code) => {
//access_token: and refresh_token:
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
console.log('getToken')
console.log(token)
oAuth2Client.setCredentials(token);
console.log(oAuth2Client)
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
});
}).catch((err) => {
console.log(err)
})
}
// initOAuthClient
function showAccessToken(token) {
console.log(token)
}
}
credentials file
{
"installed": {
"client_id": "*******17079-*********gjlh6g2nnndhqotn3ij509k.apps.googleusercontent.com",
"project_id": "quickstart-**********",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "*********dNz3Gceo9F",
"redirect_uris": [
"urn:ietf:wg:oauth:2.0:oob",
"http://localhost"
]
}
}

iOS Ionic Okta Login Issue - NSURLConnection code 1004

I am currently attempting to deploy an app that uses ionic + okta. I actually started with the ionic + okta template which can be found here: https://github.com/oktadeveloper/okta-ionic-auth-example
import { Component, ViewChild } from '#angular/core';
import { IonicPage, NavController } from 'ionic-angular';
import { JwksValidationHandler, OAuthService } from 'angular-oauth2-oidc';
import * as OktaAuth from '#okta/okta-auth-js';
import { TabsPage } from '../tabs/tabs';
declare const window: any;
#IonicPage()
#Component({
selector: 'page-login',
templateUrl: 'login.html'
})
export class LoginPage {
#ViewChild('email') email: any;
private username: string;
private password: string;
private error: string;
constructor(public navCtrl: NavController, private oauthService: OAuthService) {
oauthService.redirectUri = 'http://localhost:8100';
oauthService.clientId = '0oabqsotq17CoayEm0h7';
oauthService.scope = 'openid profile email';
oauthService.issuer = 'https://dev-158606.oktapreview.com/oauth2/default';
oauthService.tokenValidationHandler = new JwksValidationHandler();
// Load Discovery Document and then try to login the user
this.oauthService.loadDiscoveryDocument().then(() => {
this.oauthService.tryLogin();
});
}
ionViewDidLoad(): void {
setTimeout(() => {
this.email.setFocus();
}, 500);
}
login(): void {
this.oauthService.createAndSaveNonce().then(nonce => {
const authClient = new OktaAuth({
clientId: this.oauthService.clientId,
redirectUri: this.oauthService.redirectUri,
url: 'https://dev-158606.oktapreview.com',
issuer: 'default'
});
return authClient.signIn({
username: this.username,
password: this.password
}).then((response) => {
if (response.status === 'SUCCESS') {
return authClient.token.getWithoutPrompt({
nonce: nonce,
responseType: ['id_token', 'token'],
sessionToken: response.sessionToken,
scopes: this.oauthService.scope.split(' ')
})
.then((tokens) => {
const idToken = tokens[0].idToken;
const accessToken = tokens[1].accessToken;
const keyValuePair = `#id_token=${encodeURIComponent(idToken)}&access_token=${encodeURIComponent(accessToken)}`;
this.oauthService.tryLogin({
customHashFragment: keyValuePair,
disableOAuth2StateCheck: true
});
this.navCtrl.push(TabsPage);
});
} else {
throw new Error('We cannot handle the ' + response.status + ' status');
}
}).fail((error) => {
console.error(error);
this.error = error.message;
});
});
}
redirectLogin() {
this.oktaLogin().then(success => {
const idToken = success.id_token;
const accessToken = success.access_token;
const keyValuePair = `#id_token=${encodeURIComponent(idToken)}&access_token=${encodeURIComponent(accessToken)}`;
this.oauthService.tryLogin({
customHashFragment: keyValuePair,
disableOAuth2StateCheck: true
});
this.navCtrl.push(TabsPage);
}, (error) => {
this.error = error;
});
}
oktaLogin(): Promise<any> {
return this.oauthService.createAndSaveNonce().then(nonce => {
let state: string = Math.floor(Math.random() * 1000000000).toString();
if (window.crypto) {
const array = new Uint32Array(1);
window.crypto.getRandomValues(array);
state = array.join().toString();
}
return new Promise((resolve, reject) => {
const oauthUrl = this.buildOAuthUrl(state, nonce);
const browser = window.cordova.InAppBrowser.open(oauthUrl, '_blank',
'location=no,clearsessioncache=yes,clearcache=yes');
browser.addEventListener('loadstart', (event) => {
if ((event.url).indexOf('http://localhost:8100') === 0) {
browser.removeEventListener('exit', () => {});
browser.close();
const responseParameters = ((event.url).split('#')[1]).split('&');
const parsedResponse = {};
for (let i = 0; i < responseParameters.length; i++) {
parsedResponse[responseParameters[i].split('=')[0]] =
responseParameters[i].split('=')[1];
}
const defaultError = 'Problem authenticating with Okta';
if (parsedResponse['state'] !== state) {
reject(defaultError);
} else if (parsedResponse['access_token'] !== undefined &&
parsedResponse['access_token'] !== null) {
resolve(parsedResponse);
} else {
reject(defaultError);
}
}
});
browser.addEventListener('exit', function (event) {
reject('The Okta sign in flow was canceled');
});
});
});
}
buildOAuthUrl(state, nonce): string {
return this.oauthService.issuer + '/v1/authorize?' +
'client_id=' + this.oauthService.clientId + '&' +
'redirect_uri=' + this.oauthService.redirectUri + '&' +
'response_type=id_token%20token&' +
'scope=' + encodeURI(this.oauthService.scope) + '&' +
'state=' + state + '&nonce=' + nonce;
}
}
I can build the app using ionic cordova build ios and load it into xcode. It is properly signed and loads on my phone. I can click through to the okta login page, but when I attempt to log in, I get redirected back to the login page, and get the following errors in xcode:
API error: returned 0 width
nw_socket_connect connectx SAE_ASSOCID_ANY 0, Null, 0, Null, failed
connectx failed
TIC TCP Conn Failed Err(61)
NSURLConnection finished with error - code 1004
webView:didFailLoadWithError - -1004: Could not connect to the server
ERROR: ERROR Error: Uncaught (in promise): Error: Parameter jwks expected!
ValidateSignature#http://localhost...
ProcessIdToken#http://localhost...
...
I didn't modify the login code other than to use setRoot vs push for the successful redirect. The app works when I run it in browser or the ionic lab. I noticed that the source code uses localhost as an event url. I assume that's the address of the cordova browser - does this need to change?
I feel like it's a setting in xcode. I am trying to build for iOS9 with xcode10.

Swagger UI with Swashbuckle not displaying OAuth2 option

I've got a Web API which I've secured with OAuth2 but I'm having difficulty getting Swagger UI to show the authentication option.
Currently, the api_key section of UI still shows, despite there being no configuration for it.
Here's my SwaggerConfig
public class SwaggerConfig
{
public static void Register()
{
if (ConfigUtil.SSOSupported)
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
// Swashbuckle.Application.OAuth2SchemeBuilder
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.Schemes(new[] { "http", "https" });
c.SingleApiVersion("v1", "API Adapter");
c.PrettyPrint();
c.ApiKey(string.Empty);
c.OAuth2("oauth2")
.Description("Description here")
.Flow("implicit")
.AuthorizationUrl(ConfigUtil.SSOAuthority() + "/connect/authorize")
.Scopes(scopes =>
{
scopes.Add("api", "api");
});
c.IgnoreObsoleteProperties();
c.DescribeAllEnumsAsStrings();
})
.EnableSwaggerUi(c =>
{
c.EnableOAuth2Support("your-client-id", "your-client-secret-if-required", "your-realms", "your-app-name");
});
}
}
}
This section is included in page source after loading:
window.swashbuckleConfig = {
rootUrl: 'http://localhost:44390',
discoveryPaths: arrayFrom('swagger/docs/v1'),
booleanValues: arrayFrom('true|false'),
validatorUrl: stringOrNullFrom(''),
customScripts: arrayFrom(''),
docExpansion: 'none',
supportedSubmitMethods: arrayFrom('get|put|post|delete|options|head|patch'),
oAuth2Enabled: ('true' == 'true'),
oAuth2ClientId: 'your-client-id',
oAuth2ClientSecret: 'your-client-secret-if-required',
oAuth2Realm: 'your-realms',
oAuth2AppName: 'your-app-name',
oAuth2ScopeSeperator: ' ',
oAuth2AdditionalQueryStringParams: JSON.parse('{}'),
apiKeyName: 'api_key',
apiKeyIn: 'query'
};
And the iniOAuth function is called:
window.swaggerUi = new SwaggerUi({
url: swashbuckleConfig.rootUrl + "/" + swashbuckleConfig.discoveryPaths[0],
dom_id: "swagger-ui-container",
booleanValues: swashbuckleConfig.booleanValues,
supportedSubmitMethods: swashbuckleConfig.supportedSubmitMethods,
onComplete: function(swaggerApi, swaggerUi){
if (typeof initOAuth == "function" && swashbuckleConfig.oAuth2Enabled) {
initOAuth({
clientId: swashbuckleConfig.oAuth2ClientId,
clientSecret: swashbuckleConfig.oAuth2ClientSecret,
realm: swashbuckleConfig.oAuth2Realm,
appName: swashbuckleConfig.oAuth2AppName,
scopeSeparator: swashbuckleConfig.oAuth2ScopeSeperator,
additionalQueryStringParams: swashbuckleConfig.oAuth2AdditionalQueryStringParams
});
But I just get the api_key text box and no Authorize button like I'd expect.
The controllers and actions all display without an issue.
I'm sure it's something small, but I've been fiddling for a couple of days now and have run out of ideas.

Resources