How do I authenticate with Azure, using a mobile app (without relying on interactive sign-in)?
Expectations:
As I understand, the following values are required for authenticating with Azure resources prior to invoking them:
• TenantId • ClientId • Secret • Scope
Issue:
The following documentation suggests that a mobile app user is required to manually authenticate with Azure before using the mobile app.
In my case, the mobile app itself is intended to be the user/client of Azure resources. Thus, I do not want to confuse an end-user with a sign-in window to use Azure resources.
I expected TenantId, ClientId, Secret, and Scope to be all that is required for the mobile app to authenticate (without an interactive sign-in for the end-user).
Summary:
In conclusion, how can I authenticate with Azure using a mobile app without relying on interactive sign-in?
Client:
var tenantId = <some value>;
var clientId = <some value>;
var secret = <some value>;
var scope = "https://management.azure.com/.default";
WebGateway.authToken = await BearerToken.Create(tenantId, clientId, secret, scope);
var serviceKeysResponse = await WebGateway.postToAsync(someBaseUrl, someResource, somePayload);
...
Bearer Token:
public static class BearerToken
{
public async static Task<string> Create(string tenantId, string clientId, string clientSecret, string scope)
{
var tokenRequestBody = new Dictionary<string, string> {
{ "grant_type" , "client_credentials" },
{ "client_id" , clientId },
{ "client_secret", clientSecret },
{ "scope" , scope }
};
var url = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";
var client = new HttpClient() { BaseAddress = new Uri(url) };
var content = new FormUrlEncodedContent(tokenRequestBody);
var response = await client.PostAsync("", content);
if (response.IsSuccessStatusCode)
{
var tokenResponse = await response.Content.ReadAsStringAsync();
var valueFor = JsonConvert.DeserializeObject<Root>(tokenResponse);
return valueFor.access_token;
}
throw new Exception(response.ReasonPhrase);
}
public class Root
{
public string token_type { get; set; }
public int expires_in { get; set; }
public int ext_expires_in { get; set; }
public string access_token { get; set; }
}
}
HTTP Request:
let postToAsync (baseAddress:string) (resource:string) (payload:Object) =
task {
let tokenSource = new CancellationTokenSource(TimeSpan(0,3,0));
let token = tokenSource.Token;
try
Debug.WriteLine $"\n\nAttempting HTTP Post: {baseAddress}{resource}"
use client = httpClient baseAddress
client.DefaultRequestHeaders.Authorization <- AuthenticationHeaderValue("Bearer", authToken)
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, content, token) |> Async.AwaitTask
Debug.WriteLine $"\n\n{response.StatusCode}:\n{baseAddress}{resource}\n\n"
return response
with ex ->
Debug.WriteLine($"\n\nError: {baseAddress}{resource}\n{ex.GetBaseException().Message}")
if ex.GetBaseException().Message = "timeout" || ex.GetBaseException().Message = "A task was canceled"
then return new HttpResponseMessage(HttpStatusCode.RequestTimeout)
else return new HttpResponseMessage(HttpStatusCode.BadRequest)
}
You are using OAuth2 under the hood.
The application have to implement one workflow to retrieve an authorization token, which is not the workflow to authenticate a user. This is left to the authorization server and unspecified, most provider implements OpenID Connect that makes your application redirect to the identity provider website (Google, Amazon, Facebook, Apple, Microsoft, StackExchange, ...). I think that your toolkit is implementing all the complicated stuff for you by the look of your code samples.
You are authenticating the OAuth2 client: the mobile application. I don't know your exact use-case but client ran on untrusted (consumer) systems should be public clients that doesn't carry a secret. You should evaluate if you need, in OAuth2 definitions, a public or confidential client.
There are possible ways but you will certainly drastically lower authentication strengths and security. I think you are in a XY problem and want instead to solve this problem.
I suggest that you inform the user that they are going to authenticate on Azure resources. Then they won't be confused by going into the third-party authentication workflow. Authentication is hard to get right, focus on the application instead and let your third-party handle that for you.
This way is much more secure and flexible than passwords, where current best practices are to pair these with OTP, secure key, biometry or whatever additional factor you would have to implement since a secure provider won't offer password only authentication.