I'm working on a Flutter app where I need to implement a custom login screen that directly uses a username and password for authentication with Azure AD B2C, bypassing the default Microsoft login UI. The goal is to allow users to input their credentials directly into our app's interface.
Problem However, when trying to acquire tokens using the Resource Owner Password Credential (ROPC) flow, I'm encountering a 404 error. Here is the code:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';
void main() {
HttpOverrides.global = MyHttpOverrides();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginPage(),
);
}
}
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
void _login() async {
final String b2cTenantName = 'XXXXtest'; // Replace with your B2C tenant name
final String userFlowName = 'B2C_1_ROPC_Auth'; // Replace with your User Flow name
final String clientId = '14096713-1c23-XXXX-8284-XXf2b6a5e22e'; // Replace with your App ID
final String scope = 'openid offline_access $clientId';
final String username = _usernameController.text;
final String password = _passwordController.text;
try {
var response = await http.post(
Uri.parse('https://$b2cTenantName.b2clogin.com/$b2cTenantName.onmicrosoft.com/$userFlowName/oauth2/v2.0/token'),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: {
'client_id': clientId,
'grant_type': 'password',
'scope': scope,
'username': username,
'password': password,
'response_type': 'token id_token'
},
);
if (response.statusCode == 200) {
var data = json.decode(response.body);
print('response.statusCode ${response.statusCode}');
print('response.body ${response.body}');
_showDialog('Login Successful', 'Access Token: ${data['access_token']}');
} else {
print('response.statusCode ${response.statusCode}');
_showDialog('Login Failed', 'Failed to log in. Please check your credentials and try again.');
}
} catch (e) {
print('response.e ${e}');
_showDialog('Login Error', 'An error occurred: $e');
}
}
void _showDialog(String title, String content) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
ElevatedButton(
child: Text('OK'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login')),
body: Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _usernameController,
decoration: InputDecoration(labelText: 'Username'),
),
SizedBox(height: 10),
TextField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _login,
child: Text('Login'),
),
],
),
),
);
}
}
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
}
}
I've tried many approaches and even used code from Stack Overflow, but nothing has worked. Please help me if you know the solution. "
A new attempt: Here are the steps I followed.
The user flow name is the same as the one you added.
when run flutter web show this error
and when run on android show this error
I/flutter (15894): response.statusCode 404
I tried all these steps. Please let me know if I made any mistakes.
user flow
Initially, I too got same error when I ran your code in my environment like this:
To resolve the error, follow these steps where I registered one Azure AD B2C application with below account type that supports user flows:
While using ROPC flow, make sure to enable public client flow option in Authentication
tab of application:
In App registration's Manifest, enable enableAccessTokenIssuance attribute to true like this:
Now, create one resource owner user flow in your Azure AD B2C tenant like this:
Now, I used below modified code in my environment that asked user to enter credentials and gave access token successfully like this:
main.dart:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';
void main() {
HttpOverrides.global = MyHttpOverrides();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginPage(),
);
}
}
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
void _login() async {
final String b2cTenantName = 'infrab2c'; // Replace with your B2C tenant name
final String userFlowName = 'B2C_1_ROPC_Auth'; // Replace with your User Flow name
final String clientId = '7983c484-db70-xxx-bc71-xxxxx'; // Replace with your App ID
final String scope = 'openid offline_access $clientId';
final String username = _usernameController.text;
final String password = _passwordController.text;
try {
var response = await http.post(
Uri.parse('https://$b2cTenantName.b2clogin.com/$b2cTenantName.onmicrosoft.com/$userFlowName/oauth2/v2.0/token'),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: {
'client_id': clientId,
'grant_type': 'password',
'scope': scope,
'username': username,
'password': password,
'response_type': 'token id_token'
},
);
if (response.statusCode == 200) {
var data = json.decode(response.body);
print('response.statusCode ${response.statusCode}');
print('response.body ${response.body}');
_showDialog('Login Successful', 'Access Token: ${data['access_token']}');
} else {
print('response.statusCode ${response.statusCode}');
_showDialog('Login Failed', 'Failed to log in. Please check your credentials and try again.');
}
} catch (e) {
print('response.e ${e}');
_showDialog('Login Error', 'An error occurred: $e');
}
}
void _showDialog(String title, String content) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
ElevatedButton(
child: Text('OK'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login')),
body: Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _usernameController,
decoration: InputDecoration(labelText: 'Username'),
),
SizedBox(height: 10),
TextField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _login,
child: Text('Login'),
),
],
),
),
);
}
}
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
}
}
Response:
Reference:
Set up a resource owner password credentials flow - Azure AD B2C | Microsoft