Swift Auth0 access code results in unauthorized access but nothing in Auth0 logs - node.js

I have a nestJS backend protected with Auth0. I am able to successfully access the backend from react-admin by including the access token in the authorization header (Authorization: Bearer ACCESS_TOKEN)
However, I seem to have an issue when trying to access the same backend from a Swift iOS app. I have followed the Auth0 tutorials and am able to confirm successful user login and access to user profile. However, when I try to make a request to the nestJS backend, I receive a 401 Unauthorized error. Interestingly, nothing is recorded in the Auth0 logs.
Link to Auth0 tutorial: https://auth0.com/docs/quickstart/native/ios-swift/04-calling-apis
let path = "\(baseURL)\(endpoint.rawValue)"
guard let url = URL(string: path)
else { preconditionFailure("Bad URL") }
var headers: [String:String] = [:]
headers["Content-Type"] = "application/json"
// if access token is set then set Authorization headers
if (accessToken != nil) {
headers["Authorization"] = "Bearer \(accessToken!)"
print("Bearer \(accessToken!)")
}
var request = URLRequest(url: url)
request.httpMethod = "\(method)"
request.allHTTPHeaderFields = headers
// check if body exists
if (body != nil) {
request.httpBody = body!
}
let dataTask = URLSession.shared.dataTask(with: request) {
(data, response, error) in
guard error == nil
else { completion(.failure(.serverError)); return }
do {
guard let data = data
else { completion(.failure(.serverError)); return }
guard let object : [[String: AnyObject]] = try JSONSerialization.object(with: data) as? [[String: AnyObject]]
else {
print("Unable to convert from data")
return
}
guard let json = try? JSONSerialization.data(withJSONObject: object, options: .prettyPrinted)
else {
print("Unable to prettify")
return
}
guard let jsonString = String(data: json, encoding: .utf8)
else {
print("Unable to convert to string")
return
}
print("JSON: \(jsonString)")
completion(Result.success(object))
} catch {
completion(Result.failure(.parsingError))
}
}
dataTask.resume()
baseURL is a string that points to my nestJS backend.
endpoint is an enum of endpoints, for example \user
Using Proxyman I am able to confirm that the endpoint is hit with the correct headers. Screenshot below.
Additionally, using postman I am able to successfully login and also make a get request to protected data. Screenshot below.
Any ideas to what might be the cause? Let me know if I should add any additional details.
UPDATE
I decoded the successful (react-admin) and unsuccessful (iOS) JWT tokens and noticed the following differences:
aud in the successful JWT contains an array of audiences that include the API registerd on Auth0 as well as an auth0 endpoint https://xxxxx.us.auth0.com/userinfo
azp is only present in the successful JWT and contains my clientID
aud in the unsuccessful token contains the clientID
scope and permissions is missing from unsuccessful token.
Ps. also posted on Auth0 Community
https://community.auth0.com/t/access-token-when-obtained-from-ios-results-in-401-unauthorized-while-from-react-admin-is-ok/71115

The problem was that audience was set when requesting access tokens from the react-admin, while I did not include this in the swift login implementation.
Decoding the JWT on jwt.io and the following thread lead to this conclusion.
https://community.auth0.com/t/access-token-too-short-jwt-malformed/9169/11?u=kennethphough
Adding the audience in the following code resulted in the correct jwt being returned and successful access to backend.
Auth0
.authentication()
.login(
usernameOrEmail: self.email,
password: self.password,
realm: "Username-Password-Authentication",
audience: "<YOUR_AUDIENCE>", // <- This is what I forgot
scope: "openid profile email"
)

Related

Mobile app performing an HTTP Post to Azure Function using Bearer Token and Function Key returns Unauthorized

I'm using a mobile app and am receiving an Unauthorized response when attempting to post to an Azure Function and providing a function key.
Error:
StatusCode: 401, ReasonPhrase: 'Unauthorized'
Code:
let postToAsync (baseAddress:string) (resource:string) (payload:Object) =
async {
let tokenSource = new CancellationTokenSource(TimeSpan(0,0,30));
let token = tokenSource.Token;
try
let tokens = resource.Split("?code=")
let functionKey = tokens.[1]
use client = httpClient baseAddress
client.DefaultRequestHeaders.Add("x-functions-key", functionKey)
client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue("application/json"))
let json = JsonConvert.SerializeObject(payload)
let content = new StringContent(json, Encoding.UTF8, "application/json")
let! response = client.PostAsync(resource.Replace($"?code={functionKey}",""), content, token) |> Async.AwaitTask
Debug.WriteLine $"\n\n{baseAddress}{resource}\nSuccess: {response.IsSuccessStatusCode}\n\n"
return response
with ex -> ...
} |> Async.StartAsTask
Note:
My Azure Function's AuthorizationLevel is set to Function.
I can call the function successfully when I publish it manually from Visual Studio.
However, when I deploy the function using Pulumi, I receive an Unauthorized response. I believe this is because Pulumi constrains me to add access policies for each Function App.
Versioning:
<TargetFramework>net6.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
oauth2/v2.0:
I think the following link provides a clue to why I'm observing the issue. However, I still don't know how to resolve it.
Connectivity
I launched Log Stream and observed that the URL is correct:
Access Control:
Please note that the difference between the Function App that I created without using Pulumi, which lets me post successfully, versus the Function App that was generated using Pulumi, is an Access Policy per Function App with Pulumi.
public static class AccessPolicies
{
public static void Build(string policyName, string functionName, Dictionary<string, CustomResource> registry)
{
var resourceGroup = registry[nameof(ResourceGroup)] as ResourceGroup;
var keyVault = registry[nameof(KeyVault)] as KeyVault;
var functionApp = registry[functionName] as FunctionApp;
var result = new AccessPolicy(policyName, new AccessPolicyArgs {
KeyVaultId = keyVault.Id,
TenantId = TenantId.Value,
ObjectId = functionApp.Identity.Apply(v => v.PrincipalId ?? "11111111-1111-1111-1111-111111111111"),
KeyPermissions = new[] { "Get", },
SecretPermissions = new[] { "Get", },
});
registry.Add($"{policyName}-{functionName}", result);
}
}
}
I tried to reproduce the same in my environment via Postman and got below results:
I have one function app with http function named srifunction like below:
I generated one bearer token with same scope as you like below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
grant_type:client_credentials
client_id: <appID>
client_secret: <secret_value>
scope: https://management.azure.com/.default
Response:
When I used the above token to call function, I got 401 Unauthorized error same as you like below:
POST https://<funcappName>.azurewebsites.net/api/<function_name>
Authorization: Bearer <token>
If you pass function key in token value, you will still get 401 Unauthorized error like below:
POST https://<funcappName>.azurewebsites.net/api/<function_name>
Authorization: Bearer <function key>
To call function using function key, you need to include key value
in x-functions-key header instead of Bearer token.
When I included the above header, I am able to call the function successfully like below:
POST https://<funcappName>.azurewebsites.net/api/<function_name>
x-functions-key: <function key>

Owin not authenticated when requesting access token

I'm using implicit grant type, and when I request "id_token token" as response type my HttpContext.Current.User is null after logging in leading me to believe something has gone wrong inside owin. If I just have "id_token" as response type its fine. Do I need to tell owin somewhere to get the access token?
For reference I'm using .Net Framework as my client and identityserver4.
To be able to get the token via browser you need to set AllowAccessTokensViaBrowser = true on client's config within IdentityServer:
new Client
{
...
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
...
},
and on MVC client's Startup, to you can add the access_token as a claim to user:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
...
ResponseType = "id_token token",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
return Task.FromResult(0);
}
}
});
I have the full working sample here

Verify JWT from Google Chat POST request

I have a bot in NodeJS connected to Google Chat using HTTPs endpoints. I am using express to receive requests. I need to verify that all requests come from Google, and want to do this using the Bearer Token that Google Sends with requests.
My problem is that I am struggling to find a way to verify the tokens.
I have captured the token and tried a GET reuqes to https://oauth2.googleapis.com/tokeninfo?id_token=ey... (where ey... is the token start).
Which returns:
"error": "invalid_token",
"error_description": "Invalid Value"
}
I have tried what Google recommends:
var token = req.headers.authorization.split(/[ ]+/);
client.verifyIdToken({
idToken: token[1],
audience: JSON.parse(process.env.valid_client_ids)
}).then((ticket) => {
gchatHandler.handleGChat(req.body, res);
}).catch(console.error);
And get the following error:
Error: No pem found for envelope: {"alg":"RS256","kid":"d...1","typ":"JWT"}
Any idea where I should head from here?
Edit: https://www.googleapis.com/service_accounts/v1/metadata/x509/chat#system.gserviceaccount.com found this, investigating how to use it. The kid matches the one I get.
Worked it out, eventually.
You need to hit: https://www.googleapis.com/service_accounts/v1/metadata/x509/chat#system.gserviceaccount.com to get a JSON file containing the keys linked to their KIDs.
Then when a request arrives, use jsonwebtoken (NPM) to decode the token and extract the KID from the header.
Use the KID to find the matching public key in the response from the website above, then use the verify function to make sure the token matches the public key.
You also need to pass the audience and issuer options to verify, to validate that it is your particular service account hitting the bot.
The solution above maybe the correct for Google Chat, but in my experience Google services (e.g. Google Tasks) use OIDC tokens, which can be validated with verifyIdToken function.
Adding my solution here, since your question/answer was the closest thing I was able to find to my problem
So, In case if you need to sign a request from your own code
on client, send requests with OIDC token
import {URL} from 'url';
import {GoogleAuth} from 'google-auth-library';
// will use default auth or GOOGLE_APPLICATION_CREDENTIALS path to SA file
// you must validate email of this identity on the server!
const auth = new GoogleAuth({});
export const request = async ({url, ...options}) => {
const targetAudience = new URL(url as string).origin;
const client = await auth.getIdTokenClient(targetAudience);
return await client.request({...options, url});
};
await request({ url: 'https://my-domain.com/endpoint1', method: 'POST', data: {} })
on the server, validate OIDC (Id token)
const auth = new OAuth2Client();
const audience = 'https://my-domain.com';
// to validate
const token = req.headers.authorization.split(/[ ]+/)[1];
const ticket = await auth.verifyIdToken({idToken: token, audience });
if (ticket.getPayload().email !== SA_EMAIL) {
throw new Error('request was signed with different SA');
}
// all good
Read more about Google OpenID Connect Tokens

Does Keycloak allow obtaining id tokens via web interface

I am investigating how to possibly authenticate to a Kubernetes 1.13 cluster with OpenID Connect and Keycloak. I am new to this area.
This YouTube video ("Use Open ID Connect for Kubernetes API server") accomplishes part of what I want. An id token is initially obtained by making a HTTP request (with curl) to Keycloak citing grant type password. The resulting token is then subsequently used in further HTTP requests to the Kubernetes API. This works but has the disadvantage that clients directly handle users' permanent credentials.
Would it not be better if the token were issued by a secure web page that also required authentication via Keycloak (this time with grant type authorization code) and did nothing else but displaying a new token? Such tokens (transient credentials) could then e.g. be manually copied into kubeconfigs for further use?
Does Keycloak provide such interactive web pages (next to the REST endpoints for obtaining tokens programatically) or is this out of scope? If the second, are there other standard components for such tasks?
UPDATE This illustration from the Kubernetes documentation perhaps makes more clear what I am seeking. In step 1 a user should log into the Identity provider to obtain tokens which can then be configured into kubectl. Does Keycloak support this step, i.e. offer a web page where users could log in to obtain their tokens?
If I am able to understand your question ,so you want to get the accesstoken via Java code so here is code you can write and call
String obtainAccessToken = obtainAccessToken(username, password);
putRequest.addHeader("Authorization", "bearer " + obtainAccessToken);
putRequest.addHeader("content-type", MediaType.APPLICATION_JSON);
Here is the method you should call
public String obtainAccessToken(String UserName, String pwd)
{
AuthzClient authzClient = AuthzClient.create(configuration);
AccessTokenResponse accessTokenResponse = authzClient.obtainAccessToken(UserName, pwd);
String token = accessTokenResponse.getToken();
return token;
}
Here is the get realm method
public Response getAllRealms() {
ObjectMapper mapper = JacksonObjectMapperProvider.getObjectMapper();
CloseableHttpResponse response = null;
List<SureRealmRepresentation> realmList = new ArrayList<SureRealmRepresentation>();
int status;
try {
String urlGetAllRealms = URL + "/admin/realms";
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet getRequest = new HttpGet(urlGetAllRealms);
String obtainAccessToken = obtainAccessToken(username, password);
getRequest.addHeader("Authorization", "bearer " + obtainAccessToken);
getRequest.addHeader("content-type", MediaType.APPLICATION_JSON);
response = httpclient.execute(getRequest);
status = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
if (status == 200) {
RealmRepresentation[] realmArray = mapper.readValue(responseBody, RealmRepresentation[].class);
}
catch (Exception e) {
if (e instanceof Exception) {
throw (Exception) e;
} else {
throw ErrorHandler.wrap(new Exception("EroorType : "+ e.toString()));
}
}

MVC 5 OWIN External Login with Mobile Services

I am doing external login (Facebook, Twitter, Microsoft) using MVC 5 OWIN Identity 2, which works great, but I need to access a mobile services with this credential, I have read that to this I need a access token, so I get the access token and try to pass it to the mobile services, but always has this error:
Facebook: Error:
The Facebook Graph API access token authorization request failed with HTTP status code 400
Microsoft: Error:
Invalid token format. Expected Envelope.Claims.Signature.
The method that I am trying to use with mobile services is:
await mobileservi.LoginAsync(MobileServiceAuthenticationProvider.[ProviderName], token);
I read on this link:
http://msdn.microsoft.com/en-us/library/dn296411.aspx
So I am using a JObject() to pass the access token
The format of the token that I most pass:
For Microsoft is:
token.Add("authenticationToken", _accessToken);
{"authenticationToken":"<authentication_token>"}
For Facebook is:
token.Add("access_token", _accessToken);
{"access_token":"<access_token>"}
But I do not have the format for Twitter.
Now according to Azure Mobile Services documentation, I most use the azure mobile services URL on my apps for any of this providers, but if I do this, I receive an error of incorrect URL when redirecting to the provider log in page.
I read this post with OAuth:
http://blogs.msdn.com/b/carlosfigueira/archive/2013/06/25/exposing-authenticated-data-from-azure-mobile-services-via-an-asp-net-mvc-application.aspx
It has to be something like this for MVC 5 OWIN Identity 2.
On the Startuo.Auth.cs file, I have this configure to get the access token for each provider:
Microsoft:
var MicrosoftOption = new MicrosoftAccountAuthenticationOptions()
{
ClientId = "0000000048124A22",
ClientSecret = "c-gTye48WE2ozcfN-bFMVlL3y3bVY8g0",
Provider = new MicrosoftAccountAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim(("urn:microsoftaccount:access_token", context.AccessToken, XmlSchemaString, "Microsoft"));
return Task.FromResult(0);
}
}
};
Twitter:
var twitterOption = new TwitterAuthenticationOptions()
{
ConsumerKey = "ConsumerKey",
ConsumerSecret = "ConsumerSecret",
Provider = new TwitterAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim("urn:tokens:twitter:accesstoken", context.AccessToken));
context.Identity.AddClaim(new Claim("urn:tokens:twitter:accesstokensecret", context.AccessTokenSecret));
return Task.FromResult(0);
}
}
};
Facebook:
var facebookOption = new FacebookAuthenticationOptions()
{
AppId = "AppId",
AppSecret = "AppSecret",
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook"));
return Task.FromResult(0);
}
}
};
On the externalLoginCallback, this is how a retrieve the access token
string email = null;
string accessToken = null;
ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
switch (login.LoginProvider)
{
case "Facebook":
accessToken = ext.Claims.First(x => x.Type.Contains("access_token")).Value;
break;
case "Twitter":
accessToken = ext.Claims.First(x => x.Type.Contains("accesstoken")).Value;
break;
case "Microsoft":
accessToken = ext.Claims.First(x => x.Type.Contains("access_token")).Value;
break;
}
Later I store this value on a session variable, this value is the one that I use to pass as the access token.
So I have no idea what to do, can anyone please help me?
OK, I found what I was doing wrong, in order to respect the authorization flow, I must have APP ID and APP Secret that I register on my app (Google, Facebook, Microsoft, Twitter), on my mobile service. This is the important part, the register URL in the app must be the URL of the web site, after doing this, everything work fine

Resources